Hi Jim, > @smallexample > #undef memcpy > #define bos0(dest) __builtin_object_size (dest, 0) > #define memcpy(dest, src, n) \ > __builtin___memcpy_chk (dest, src, n, bos0 (dest)) > ... > Such built-in functions are provided for @code{memcpy}, @code{mempcpy}, > @code{memmove}, @code{memset}, @code{strcpy}, @code{stpcpy}, @code{strncpy}, > @code{strcat} and @code{strncat}. > > There are also checking built-in functions for formatted output functions. > @smallexample > int __builtin___sprintf_chk (char *s, int flag, size_t os, const char *fmt, > ...);
Indeed, this __builtin_object_size is useful here, because it works also on pointers, whereas sizeof() gives the information only on arrays. By combining both, we can get checking in 5 out of 6 cases in the attached sample. glibc uses __builtin_object_size for its Fortify support, in the files /usr/include/bits/socket2.h /usr/include/bits/stdio2.h /usr/include/bits/stdlib.h /usr/include/bits/string3.h /usr/include/bits/unistd.h /usr/include/bits/wchar2.h and conditionalized by #if __USE_FORTIFY_LEVEL > 0 There are many functions to which 'char *' pointers are being passed. For memcpy, memset, memmove, and so on, there is already a size_t argument. The kinds of errors that the *_chk functions can check for these are only really dumb beginner mistakes. It's OK for glibc to do that, but I think it's not necessary for gnulib. But for functions that don't take a size_t argument, such as strcat readlink realpath forkpty openpty sprintf, u*_sprintf vsprintf, u*_vsprintf mkdtemp mkostemp mkstemp mkstemps mkstemp_safer mkostemp_safer mkostemps_safer mkstemps_safer inttostr these *_chk functions can point out some unobvious mistakes. Among these, gnulib doesn't replace most of them on glibc systems, and we need the warnings only on glibc systems. So what we need to handle in gnulib is only essentially u*_sprintf u*_vsprintf mkstemp_safer mkostemp_safer mkostemps_safer mkstemps_safer inttostr Let's start with inttostr. Using the same technique as glibc in its /usr/include/bits/unistd.h file, I arrive at the attached code, which produces a compile-time warning when possible and calls the _chk function when it cannot prove that the bound it sufficient. It may look complicated, but it boils down to - a bit of macrology and inline functions in inttostr.h, - a function inttostr_chk that checks the bound, - a use of gl_ASM_SYMBOL_PREFIX in the module description. Should we pursue this path? Bruno
#include <stdint.h> #include <sys/types.h> #include "intprops.h" #include "verify.h" #ifndef __GNUC_PREREQ # if defined __GNUC__ && defined __GNUC_MINOR__ # define __GNUC_PREREQ(maj, min) \ ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) # else # define __GNUC_PREREQ(maj, min) 0 # endif #endif #if __GNUC_PREREQ (3,4) # undef __attribute_warn_unused_result__ # define __attribute_warn_unused_result__ \ __attribute__ ((__warn_unused_result__)) #else # define __attribute_warn_unused_result__ /* empty */ #endif char *imaxtostr (intmax_t, char *) __attribute_warn_unused_result__; char *inttostr (int, char *) __attribute_warn_unused_result__; char *offtostr (off_t, char *) __attribute_warn_unused_result__; char *uinttostr (unsigned int, char *) __attribute_warn_unused_result__; char *umaxtostr (uintmax_t, char *) __attribute_warn_unused_result__; char *inttostr_chk (int, char *, size_t) __attribute_warn_unused_result__; /* The value of this macro is determined by gl_ASM_SYMBOL_PREFIX. */ #define USER_LABEL_PREFIX #define _GL_STRINGIFY(s) _GL_STRINGIFY1 (s) #define _GL_STRINGIFY1(s) #s #define ASM_SYMBOL_PREFIX _GL_STRINGIFY (USER_LABEL_PREFIX) /* __attribute__ __warning__ requires GCC >= 4.3. __builtin_object_size requires GCC >= 4.1. __always_inline__ requires GCC >= 3.2. */ #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) && __USE_FORTIFY_LEVEL > 0 /* Compiler determined size of a char* pointer. or char[] array. Works via a GCC built-in for pointers into arrays of constant size, and works via sizeof() for references to arrays of variable length. Punt for pointers to arrays of variable length or unknown length. */ # define _GL_pointed_object_size(s) \ (__builtin_object_size (s, 1) != (size_t)-1 \ ? __builtin_object_size (s, 1) \ : sizeof (s) != sizeof (void *) \ ? sizeof (s) \ : (size_t)-1) # define inttostr(n, s) \ (__extension__ (inttostr_chk_inline (n, s, _GL_pointed_object_size (s)))) extern char *inttostr_chk_warn (int n, char *s, size_t size) __asm__ (ASM_SYMBOL_PREFIX "inttostr_chk"); extern __typeof__ (inttostr_chk_warn) inttostr_chk_warn __attribute__((__warning__ ("inttostr: size of destination buffer too small"))); static inline __attribute__ ((__always_inline__)) char * inttostr_chk_inline (int n, char *s, size_t size) { if (__builtin_constant_p (size)) { if (size != (size_t)-1) { /* buffer size known at compile time. */ if (size >= INT_BUFSIZE_BOUND (int)) return (inttostr) (n, s); else return inttostr_chk_warn (n, s, size); } else /* buffer size unknown. */ return (inttostr) (n, s); } /* buffer size unknown at compile time but known at run time. */ return inttostr_chk (n, s, size); } #endif /* Buffer too small: warning at compile time, and call inttostr_chk. */ static int foo1_size; void foo1 (int n) { char buf[5]; foo1_size = __builtin_object_size (buf, 1); // 5 char *result = inttostr (n, buf); } static int foo2_size; void foo2 (int n) { char buf[5]; foo2_size = __builtin_object_size (&buf[0], 1); // 5 char *result = inttostr (n, &buf[0]); } /* Buffer large enough: no warning, and call inttostr. */ static int foo3_size; void foo3 (int n) { char buf[13]; foo3_size = __builtin_object_size (buf, 1); // 13 char *result = inttostr (n, buf); } static int foo4_size; void foo4 (int n) { char buf[13]; foo4_size = __builtin_object_size (&buf[0], 1); // 13 char *result = inttostr (n, &buf[0]); } /* Buffer size unknown at compile time but known at run time. No warning, but call inttostr_chk. */ static int foo5_size; static int foo5_sizeof; void foo5 (int n) { char buf[11+(n<0)]; foo5_size = __builtin_object_size (buf, 1); // -1 foo5_sizeof = sizeof (buf); // 11+(n<0) char *result = inttostr (n, buf); } /* Buffer size unknown. No warning. Call inttostr. */ static int foo6_size; static int foo6_sizeof; void foo6 (int n) { char buf[11+(n<0)]; foo6_size = __builtin_object_size (&buf[0], 1); // -1 foo6_sizeof = sizeof (&buf[0]); // sizeof (void *) char *result = inttostr (n, &buf[0]); }