Hi,

I played around with adding
  __attribute__((malloc(free_func), malloc(another_free_func)))
annotations to a few functions in pg. See
https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html


Adding them to pg_list.h seems to have found two valid issues when compiling
without optimization:

[1001/2331 22  42%] Compiling C object 
src/backend/postgres_lib.a.p/commands_tablecmds.c.o
../../../../home/andres/src/postgresql/src/backend/commands/tablecmds.c: In 
function ‘ATExecAttachPartition’:
../../../../home/andres/src/postgresql/src/backend/commands/tablecmds.c:17966:25:
 warning: pointer ‘partBoundConstraint’ may be used after ‘list_concat’ 
[-Wuse-after-free]
17966 |                         
get_proposed_default_constraint(partBoundConstraint);
      |                         
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../../../home/andres/src/postgresql/src/backend/commands/tablecmds.c:17919:26:
 note: call to ‘list_concat’ here
17919 |         partConstraint = list_concat(partBoundConstraint,
      |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17920 |                                                                  
RelationGetPartitionQual(rel));
      |                                                                  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[1233/2331 22  52%] Compiling C object 
src/backend/postgres_lib.a.p/rewrite_rewriteHandler.c.o
../../../../home/andres/src/postgresql/src/backend/rewrite/rewriteHandler.c: In 
function ‘rewriteRuleAction’:
../../../../home/andres/src/postgresql/src/backend/rewrite/rewriteHandler.c:550:41:
 warning: pointer ‘newjointree’ may be used after ‘list_concat’ 
[-Wuse-after-free]
  550 |                                         checkExprHasSubLink((Node *) 
newjointree);
      |                                         
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../../../home/andres/src/postgresql/src/backend/rewrite/rewriteHandler.c:542:33:
 note: call to ‘list_concat’ here
  542 |                                 list_concat(newjointree, 
sub_action->jointree->fromlist);
      |                                 
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Both of these bugs seem somewhat older, the latter going back to 19ff959bff0,
in 2005.  I'm a bit surprised that we haven't hit them before, via
DEBUG_LIST_MEMORY_USAGE?


When compiling with optimization, another issue is reported:

[508/1814 22  28%] Compiling C object 
src/backend/postgres_lib.a.p/commands_explain.c.o
../../../../home/andres/src/postgresql/src/backend/commands/explain.c: In 
function 'ExplainNode':
../../../../home/andres/src/postgresql/src/backend/commands/explain.c:2037:25: 
warning: pointer 'ancestors' may be used after 'lcons' [-Wuse-after-free]
 2037 |                         show_upper_qual(plan->qual, "Filter", 
planstate, ancestors, es);
      |                         
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In function 'show_group_keys',
    inlined from 'ExplainNode' at 
../../../../home/andres/src/postgresql/src/backend/commands/explain.c:2036:4:
../../../../home/andres/src/postgresql/src/backend/commands/explain.c:2564:21: 
note: call to 'lcons' here
 2564 |         ancestors = lcons(plan, ancestors);
      |                     ^~~~~~~~~~~~~~~~~~~~~~

which looks like it might be valid - the caller's "ancestors" variable could
now be freed?  There do appear to be further instances of the issue, e.g. in
show_agg_keys(), that aren't flagged for some reason.



For something like pg_list.h the malloc(free) attribute is a bit awkward to
use, because one a) needs to list ~30 functions that can free a list and b)
the referenced functions need to be declared.

In my quick hack I just duplicated the relevant part of pg_list.h and added
the appropriate attributes to the copy of the original declaration.


I also added such attributes to bitmapset.h and palloc() et al, but that
didn't find existing bugs.  It does find use-after-free instances if I add
some, similarly it does find cases of mismatching palloc with free etc.


The attached prototype is quite rough and will likely fail on anything but a
recent gcc (likely >= 12).


Do others think this would be useful enough to be worth polishing? And do you
agree the warnings above are bugs?

Greetings,

Andres Freund
>From e05c1260ee70457e9a899d5c32e5b85702193739 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Mon, 26 Jun 2023 12:17:30 -0700
Subject: [PATCH v1 1/2] Add allocator attributes.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/include/c.h               |  9 ++++
 src/include/nodes/bitmapset.h | 40 ++++++++++-----
 src/include/nodes/pg_list.h   | 97 +++++++++++++++++++++++++++++++++++
 src/include/utils/palloc.h    | 55 ++++++++++++--------
 4 files changed, 167 insertions(+), 34 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index f69d739be57..920fdf983a1 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -295,6 +295,15 @@
 #define unlikely(x) ((x) != 0)
 #endif
 
+#if defined(__GNUC__) && !defined(__clang__)
+#define pg_malloc_attr(deallocator) malloc(deallocator)
+#define pg_malloc_attr_i(deallocator, ptr_at) malloc(deallocator, ptr_at)
+#else
+#define pg_malloc_attr(deallocator)
+#define pg_malloc_attr_i(deallocator, ptr_at)
+#endif
+
+
 /*
  * CppAsString
  *		Convert the argument to a string, using the C preprocessor.
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 14de6a9ff1e..5037f6907ec 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -78,15 +78,27 @@ typedef enum
  * function prototypes in nodes/bitmapset.c
  */
 
-extern Bitmapset *bms_copy(const Bitmapset *a);
-extern bool bms_equal(const Bitmapset *a, const Bitmapset *b);
-extern int	bms_compare(const Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_make_singleton(int x);
+extern Bitmapset *bms_add_member(Bitmapset *a, int x);
+extern Bitmapset *bms_del_member(Bitmapset *a, int x);
+extern Bitmapset *bms_add_members(Bitmapset *a, const Bitmapset *b);
+extern Bitmapset *bms_add_range(Bitmapset *a, int lower, int upper);
+extern Bitmapset *bms_int_members(Bitmapset *a, const Bitmapset *b);
+extern Bitmapset *bms_del_members(Bitmapset *a, const Bitmapset *b);
+extern Bitmapset *bms_join(Bitmapset *a, Bitmapset *b);
 extern void bms_free(Bitmapset *a);
 
-extern Bitmapset *bms_union(const Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_intersect(const Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_difference(const Bitmapset *a, const Bitmapset *b);
+#define BMS_ALLOC_ATTRIBUTES __attribute__((pg_malloc_attr(bms_free), pg_malloc_attr(bms_add_member), pg_malloc_attr(bms_del_member), pg_malloc_attr(bms_add_members), pg_malloc_attr(bms_add_range), pg_malloc_attr(bms_int_members), pg_malloc_attr(bms_del_members), pg_malloc_attr(bms_join), warn_unused_result))
+
+extern Bitmapset *bms_copy(const Bitmapset *a) BMS_ALLOC_ATTRIBUTES;
+extern bool bms_equal(const Bitmapset *a, const Bitmapset *b);
+extern int	bms_compare(const Bitmapset *a, const Bitmapset *b);
+extern Bitmapset *bms_make_singleton(int x) BMS_ALLOC_ATTRIBUTES;
+extern void bms_free(Bitmapset *a);
+
+extern Bitmapset *bms_union(const Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_intersect(const Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_difference(const Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+
 extern bool bms_is_subset(const Bitmapset *a, const Bitmapset *b);
 extern BMS_Comparison bms_subset_compare(const Bitmapset *a, const Bitmapset *b);
 extern bool bms_is_member(int x, const Bitmapset *a);
@@ -106,13 +118,13 @@ extern BMS_Membership bms_membership(const Bitmapset *a);
 
 /* these routines recycle (modify or free) their non-const inputs: */
 
-extern Bitmapset *bms_add_member(Bitmapset *a, int x);
-extern Bitmapset *bms_del_member(Bitmapset *a, int x);
-extern Bitmapset *bms_add_members(Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_add_range(Bitmapset *a, int lower, int upper);
-extern Bitmapset *bms_int_members(Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_del_members(Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_join(Bitmapset *a, Bitmapset *b);
+extern Bitmapset *bms_add_member(Bitmapset *a, int x) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_del_member(Bitmapset *a, int x) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_add_members(Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_add_range(Bitmapset *a, int lower, int upper) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_int_members(Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_del_members(Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_join(Bitmapset *a, Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
 
 /* support for iterating through the integer elements of a set: */
 extern int	bms_next_member(const Bitmapset *a, int prevbit);
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 529a382d284..285d8c2c7ed 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -632,4 +632,101 @@ extern void list_sort(List *list, list_sort_comparator cmp);
 extern int	list_int_cmp(const ListCell *p1, const ListCell *p2);
 extern int	list_oid_cmp(const ListCell *p1, const ListCell *p2);
 
+
+#define PG_LIST_ALLOC_ATTR __attribute__(( \
+  pg_malloc_attr(lappend), \
+  pg_malloc_attr(lappend_int), \
+  pg_malloc_attr(lappend_oid), \
+  pg_malloc_attr(lappend_xid), \
+  pg_malloc_attr(list_insert_nth), \
+  pg_malloc_attr(list_insert_nth_int), \
+  pg_malloc_attr(list_insert_nth_oid), \
+  pg_malloc_attr_i(lcons, 2), \
+  pg_malloc_attr_i(lcons_int, 2), \
+  pg_malloc_attr_i(lcons_oid, 2), \
+  pg_malloc_attr(list_concat), \
+  pg_malloc_attr(list_truncate), \
+  pg_malloc_attr(list_delete), \
+  pg_malloc_attr(list_delete_ptr), \
+  pg_malloc_attr(list_delete_int), \
+  pg_malloc_attr(list_delete_oid), \
+  pg_malloc_attr(list_delete_first), \
+  pg_malloc_attr(list_delete_last), \
+  pg_malloc_attr(list_delete_first_n), \
+  pg_malloc_attr(list_delete_nth_cell), \
+  pg_malloc_attr(list_delete_cell), \
+  pg_malloc_attr(list_union), \
+  pg_malloc_attr(list_append_unique), \
+  pg_malloc_attr(list_append_unique_ptr), \
+  pg_malloc_attr(list_append_unique_int), \
+  pg_malloc_attr(list_append_unique_oid), \
+  pg_malloc_attr(list_concat_unique), \
+  pg_malloc_attr(list_concat_unique_ptr), \
+  pg_malloc_attr(list_concat_unique_int), \
+  pg_malloc_attr(list_concat_unique_oid), \
+  pg_malloc_attr(list_free), \
+  pg_malloc_attr(list_free_deep), \
+  warn_unused_result))
+
+extern PG_LIST_ALLOC_ATTR List *list_make1_impl(NodeTag t, ListCell datum1);
+extern PG_LIST_ALLOC_ATTR List *list_make2_impl(NodeTag t, ListCell datum1, ListCell datum2);
+extern PG_LIST_ALLOC_ATTR List *list_make3_impl(NodeTag t, ListCell datum1, ListCell datum2,
+							 ListCell datum3);
+extern PG_LIST_ALLOC_ATTR List *list_make4_impl(NodeTag t, ListCell datum1, ListCell datum2,
+							 ListCell datum3, ListCell datum4);
+extern PG_LIST_ALLOC_ATTR List *list_make5_impl(NodeTag t, ListCell datum1, ListCell datum2,
+							 ListCell datum3, ListCell datum4,
+							 ListCell datum5);
+
+extern PG_LIST_ALLOC_ATTR List *lappend(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *lappend_int(List *list, int datum);
+extern PG_LIST_ALLOC_ATTR List *lappend_oid(List *list, Oid datum);
+extern PG_LIST_ALLOC_ATTR List *lappend_xid(List *list, TransactionId datum);
+
+extern PG_LIST_ALLOC_ATTR List *list_insert_nth(List *list, int pos, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_insert_nth_int(List *list, int pos, int datum);
+extern PG_LIST_ALLOC_ATTR List *list_insert_nth_oid(List *list, int pos, Oid datum);
+
+extern PG_LIST_ALLOC_ATTR List *lcons(void *datum, List *list);
+extern PG_LIST_ALLOC_ATTR List *lcons_int(int datum, List *list);
+extern PG_LIST_ALLOC_ATTR List *lcons_oid(Oid datum, List *list);
+
+extern PG_LIST_ALLOC_ATTR List *list_concat(List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_concat_copy(const List *list1, const List *list2);
+
+extern PG_LIST_ALLOC_ATTR List *list_truncate(List *list, int new_size);
+
+extern PG_LIST_ALLOC_ATTR List *list_delete(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_delete_ptr(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_delete_int(List *list, int datum);
+extern PG_LIST_ALLOC_ATTR List *list_delete_oid(List *list, Oid datum);
+extern PG_LIST_ALLOC_ATTR List *list_delete_first(List *list);
+extern PG_LIST_ALLOC_ATTR List *list_delete_last(List *list);
+extern PG_LIST_ALLOC_ATTR List *list_delete_first_n(List *list, int n);
+extern PG_LIST_ALLOC_ATTR List *list_delete_nth_cell(List *list, int n);
+extern PG_LIST_ALLOC_ATTR List *list_delete_cell(List *list, ListCell *cell);
+
+extern PG_LIST_ALLOC_ATTR List *list_union(const List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_union_ptr(const List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_union_int(const List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_union_oid(const List *list1, const List *list2);
+
+extern PG_LIST_ALLOC_ATTR List *list_intersection(const List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_intersection_int(const List *list1, const List *list2);
+
+extern PG_LIST_ALLOC_ATTR List *list_append_unique(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_append_unique_ptr(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_append_unique_int(List *list, int datum);
+extern PG_LIST_ALLOC_ATTR List *list_append_unique_oid(List *list, Oid datum);
+
+extern PG_LIST_ALLOC_ATTR List *list_concat_unique(List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_concat_unique_ptr(List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_concat_unique_int(List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_concat_unique_oid(List *list1, const List *list2);
+
+extern PG_LIST_ALLOC_ATTR List *list_copy(const List *oldlist);
+extern PG_LIST_ALLOC_ATTR List *list_copy_head(const List *oldlist, int len);
+extern PG_LIST_ALLOC_ATTR List *list_copy_tail(const List *oldlist, int nskip);
+extern PG_LIST_ALLOC_ATTR List *list_copy_deep(const List *oldlist);
+
 #endif							/* PG_LIST_H */
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index d1146c12351..a9e8063023f 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -65,25 +65,40 @@ extern PGDLLIMPORT MemoryContext CurrentMemoryContext;
 #define MCXT_ALLOC_NO_OOM		0x02	/* no failure if out-of-memory */
 #define MCXT_ALLOC_ZERO			0x04	/* zero allocated memory */
 
+#define pg_alloc_attributes(size_at) \
+	__attribute__((malloc, pg_malloc_attr_i(pfree, 1), alloc_size(size_at), assume_aligned(MAXIMUM_ALIGNOF), returns_nonnull, warn_unused_result))
+#define pg_alloc_noerr_attributes(size_at) \
+	__attribute__((malloc, pg_malloc_attr_i(pfree, 1), alloc_size(size_at), assume_aligned(MAXIMUM_ALIGNOF), warn_unused_result))
+#define pg_realloc_attributes(old_at, size_at) \
+	__attribute__((alloc_size(size_at), assume_aligned(MAXIMUM_ALIGNOF), \
+				   nonnull(old_at), returns_nonnull, warn_unused_result))
+#define pg_realloc_noerr_attributes(old_at, size_at) \
+	__attribute__((alloc_size(size_at), assume_aligned(MAXIMUM_ALIGNOF), \
+				   nonnull(old_at), warn_unused_result))
+#define pg_dup_attributes(source_at) \
+	__attribute__((malloc, pg_malloc_attr_i(pfree, 1), assume_aligned(MAXIMUM_ALIGNOF), returns_nonnull, nonnull(source_at), warn_unused_result))
+
+extern void pfree(void *pointer);
+
 /*
  * Fundamental memory-allocation operations (more are in utils/memutils.h)
  */
-extern void *MemoryContextAlloc(MemoryContext context, Size size);
-extern void *MemoryContextAllocZero(MemoryContext context, Size size);
-extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
+extern void *MemoryContextAlloc(MemoryContext context, Size size) pg_alloc_attributes(2);
+extern void *MemoryContextAllocZero(MemoryContext context, Size size) pg_alloc_attributes(2);
+extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size) pg_alloc_attributes(2);
 extern void *MemoryContextAllocExtended(MemoryContext context,
-										Size size, int flags);
+										Size size, int flags) pg_alloc_noerr_attributes(2);
 extern void *MemoryContextAllocAligned(MemoryContext context,
-									   Size size, Size alignto, int flags);
+									   Size size, Size alignto, int flags) pg_alloc_noerr_attributes(2);
 
-extern void *palloc(Size size);
-extern void *palloc0(Size size);
-extern void *palloc_extended(Size size, int flags);
-extern void *palloc_aligned(Size size, Size alignto, int flags);
-extern pg_nodiscard void *repalloc(void *pointer, Size size);
+extern void *palloc(Size size) pg_alloc_attributes(1);
+extern void *palloc0(Size size) pg_alloc_attributes(1);
+extern void *palloc_extended(Size size, int flags) pg_alloc_noerr_attributes(1);
+extern void *palloc_aligned(Size size, Size alignto, int flags) pg_alloc_noerr_attributes(1);
+extern pg_nodiscard void *repalloc(void *pointer, Size size) pg_realloc_attributes(1, 2);
 extern pg_nodiscard void *repalloc_extended(void *pointer,
-											Size size, int flags);
-extern pg_nodiscard void *repalloc0(void *pointer, Size oldsize, Size size);
+											Size size, int flags) pg_realloc_noerr_attributes(1, 2);
+extern pg_nodiscard void *repalloc0(void *pointer, Size oldsize, Size size) pg_realloc_attributes(1, 2);
 extern void pfree(void *pointer);
 
 /*
@@ -123,8 +138,8 @@ extern void pfree(void *pointer);
 		MemoryContextAllocZero(CurrentMemoryContext, sz) )
 
 /* Higher-limit allocators. */
-extern void *MemoryContextAllocHuge(MemoryContext context, Size size);
-extern pg_nodiscard void *repalloc_huge(void *pointer, Size size);
+extern void *MemoryContextAllocHuge(MemoryContext context, Size size)  pg_alloc_attributes(2);
+extern pg_nodiscard void *repalloc_huge(void *pointer, Size size) pg_realloc_attributes(1, 2);
 
 /*
  * Although this header file is nominally backend-only, certain frontend
@@ -152,14 +167,14 @@ extern void MemoryContextRegisterResetCallback(MemoryContext context,
  * These are like standard strdup() except the copied string is
  * allocated in a context, not with malloc().
  */
-extern char *MemoryContextStrdup(MemoryContext context, const char *string);
-extern char *pstrdup(const char *in);
-extern char *pnstrdup(const char *in, Size len);
+extern char *MemoryContextStrdup(MemoryContext context, const char *string) pg_dup_attributes(2);
+extern char *pstrdup(const char *in) pg_dup_attributes(1);
+extern char *pnstrdup(const char *in, Size len) pg_dup_attributes(1);
 
-extern char *pchomp(const char *in);
+extern char *pchomp(const char *in) pg_dup_attributes(1);
 
 /* sprintf into a palloc'd buffer --- these are in psprintf.c */
-extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2);
-extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0);
+extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2) __attribute__((malloc, returns_nonnull, warn_unused_result));
+extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0) __attribute__((warn_unused_result));
 
 #endif							/* PALLOC_H */
-- 
2.38.0

Reply via email to