Under GCC, enforce proper strbuf_local_buf() arg

Internally the strbuf_local_buf(x) macro uses sizeof(x) to determine
the size of the buffer, but this will give the wrong behaviour if x
is a pointer (char *x), not an array (char x[]).  With this change,
invoking it with a pointer will cause a compile error.

The safety check makes use of the GCC extensions: __builtin_object_size()
and __attribute__((alloc_size(n)).  Under non-GCC compilers, the safety
check will not be performed.
This commit is contained in:
Andrew Bettison 2015-11-16 17:04:46 +10:30
parent 2ddbb86cb5
commit fdc6156ec7
2 changed files with 39 additions and 9 deletions

14
mem.h
View File

@ -29,8 +29,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#define malloc(X) _serval_debug_malloc(X,__WHENCE__) #define malloc(X) _serval_debug_malloc(X,__WHENCE__)
#define calloc(X,Y) _serval_debug_calloc(X,Y,__WHENCE__) #define calloc(X,Y) _serval_debug_calloc(X,Y,__WHENCE__)
#define free(X) _serval_debug_free(X,__WHENCE__) #define free(X) _serval_debug_free(X,__WHENCE__)
void *_serval_debug_malloc(unsigned int bytes, struct __sourceloc whence); void *_serval_debug_malloc(unsigned int bytes, struct __sourceloc whence) __attribute__ ((malloc, alloc_size(1)));
void *_serval_debug_calloc(unsigned int bytes, unsigned int count, struct __sourceloc whence); void *_serval_debug_calloc(unsigned int bytes, unsigned int count, struct __sourceloc whence) __attribute__ ((malloc, alloc_size(1)));
void _serval_debug_free(void *p, struct __sourceloc whence); void _serval_debug_free(void *p, struct __sourceloc whence);
#endif #endif
@ -38,20 +38,20 @@ void _serval_debug_free(void *p, struct __sourceloc whence);
* *
* @author Andrew Bettison <andrew@servalproject.com> * @author Andrew Bettison <andrew@servalproject.com>
*/ */
void *_emalloc(struct __sourceloc, size_t bytes); void *_emalloc(struct __sourceloc, size_t bytes) __attribute__ ((malloc, alloc_size(2), returns_nonnull));
/* Equivalent to realloc(3), but logs an error before returning NULL. /* Equivalent to realloc(3), but logs an error before returning NULL.
* *
* @author Andrew Bettison <andrew@servalproject.com> * @author Andrew Bettison <andrew@servalproject.com>
*/ */
void *_erealloc(struct __sourceloc __whence, void *ptr, size_t bytes); void *_erealloc(struct __sourceloc __whence, void *ptr, size_t bytes) __attribute__ ((alloc_size(3), returns_nonnull));
/* Equivalent to malloc(3) followed by memset(3) to zerofill, but logs an error /* Equivalent to malloc(3) followed by memset(3) to zerofill, but logs an error
* before returning NULL. * before returning NULL.
* *
* @author Andrew Bettison <andrew@servalproject.com> * @author Andrew Bettison <andrew@servalproject.com>
*/ */
void *_emalloc_zero(struct __sourceloc, size_t bytes); void *_emalloc_zero(struct __sourceloc, size_t bytes) __attribute__ ((malloc, alloc_size(2), returns_nonnull));
/* Equivalent to strdup(3)/strndup(3), but logs an error before returning NULL. /* Equivalent to strdup(3)/strndup(3), but logs an error before returning NULL.
* *
@ -60,8 +60,8 @@ void *_emalloc_zero(struct __sourceloc, size_t bytes);
* *
* @author Andrew Bettison <andrew@servalproject.com> * @author Andrew Bettison <andrew@servalproject.com>
*/ */
char *_str_edup(struct __sourceloc, const char *str); char *_str_edup(struct __sourceloc, const char *str) __attribute__ ((malloc, returns_nonnull));
char *_strn_edup(struct __sourceloc, const char *str, size_t len); char *_strn_edup(struct __sourceloc, const char *str, size_t len) __attribute__ ((malloc, returns_nonnull));
#define emalloc(bytes) _emalloc(__HERE__, (bytes)) #define emalloc(bytes) _emalloc(__HERE__, (bytes))
#define erealloc(ptr, bytes) _erealloc(__HERE__, (ptr), (bytes)) #define erealloc(ptr, bytes) _erealloc(__HERE__, (ptr), (bytes))

View File

@ -1,6 +1,6 @@
/* /*
Serval string buffer primitives Serval string buffer primitives
Copyright (C) 2012 Serval Project Inc. Copyright (C) 2012-2015 Serval Project Inc.
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -238,9 +238,39 @@ typedef const struct strbuf *const_strbuf;
* printf("%s\n", temp); * printf("%s\n", temp);
* } * }
* *
* WARNING: 'buf' must name a char[] array, not a char* pointer. The following
* code is wrong:
*
* char *p = malloc(50);
* ...
* strbuf b = strbuf_local_buf(p); // ERROR!
*
* In the above example, sizeof(p) will be 8 (4 on 32-bit architectures) which
* is NOT the size of the buffer that p points to (50), and not the desired
* effect: the string in strbuf b will be limited to 7 chars in length. If the
* buffer pointed to by p were less than 8 in size, then appending to strbuf b
* would cause memory corruption and a likely SIGSEGV.
*
* If compiled with the GNU C compiler, then the above example would result in
* an error at compile time.
*
* @author Andrew Bettison <andrew@servalproject.com> * @author Andrew Bettison <andrew@servalproject.com>
*/ */
#define strbuf_local_buf(buf) strbuf_local((char*)(buf), sizeof (buf)) #ifdef __GNUC__
#define strbuf_local_buf(buf) strbuf_local((char*)(buf), (sizeof(buf) == __builtin_object_size(buf, 1)) ? sizeof(buf) : __buffer_arg_is_not_array())
#else
#define strbuf_local_buf(buf) strbuf_local((char*)(buf), sizeof(buf))
#endif
#ifdef __GNUC__
// If the following error occurs at compile time or this function is not found
// at link time, it means that the argument passed to strbuf_local_buf() was
// not an array whose size is known at compile time. The most common cause of
// this is passing a pointer as the argument. The solution is to use
// strbuf_local(b, len) instead of strbuf_local_buf(b), and supply the length
// of the buffer explicitly.
size_t __buffer_arg_is_not_array() __attribute__ ((error("argument to strbuf_local_buf() must be an array not a pointer")));
#endif
/** Initialise a strbuf with a caller-supplied backing buffer. The current /** Initialise a strbuf with a caller-supplied backing buffer. The current
* backing buffer and its contents are forgotten, and all strbuf operations * backing buffer and its contents are forgotten, and all strbuf operations