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]);
}

Reply via email to