Annotate additional APIs with GCC attribute access.

This change continues the improvements to compile-time out of bounds
checking by decorating more APIs with either attribute access, or by
explicitly providing the array bound in APIs such as tmpnam() that
expect arrays of some minimum size as arguments.  (The latter feature
is new in GCC 11.)

The only effects of the attribute and/or the array bound is to check
and diagnose calls to the functions that fail to provide a sufficient
number of elements, and the definitions of the functions that access
elements outside the specified bounds.  (There is no interplay with
_FORTIFY_SOURCE here yet.)

Tested with GCC 7 through 11 on x86_64-linux.
This commit is contained in:
Martin Sebor 2021-05-06 10:56:25 -06:00
parent 3f0808ef4c
commit 26492c0a14
16 changed files with 95 additions and 45 deletions

View File

@ -31,7 +31,7 @@ libc_hidden_weak (if_nametoindex)
stub_warning (if_nametoindex)
char *
__if_indextoname (unsigned int ifindex, char *ifname)
__if_indextoname (unsigned int ifindex, char ifname[IF_NAMESIZE])
{
__set_errno (ENOSYS);
return NULL;

View File

@ -26,13 +26,14 @@ __BEGIN_DECLS
extern int __REDIRECT (__poll_alias, (struct pollfd *__fds, nfds_t __nfds,
int __timeout), poll);
extern int __poll_chk (struct pollfd *__fds, nfds_t __nfds, int __timeout,
__SIZE_TYPE__ __fdslen);
__SIZE_TYPE__ __fdslen)
__attr_access ((__write_only__, 1, 2));
extern int __REDIRECT (__poll_chk_warn, (struct pollfd *__fds, nfds_t __nfds,
int __timeout, __SIZE_TYPE__ __fdslen),
__poll_chk)
__warnattr ("poll called with fds buffer too small file nfds entries");
__fortify_function int
__fortify_function __attr_access ((__write_only__, 1, 2)) int
poll (struct pollfd *__fds, nfds_t __nfds, int __timeout)
{
if (__glibc_objsize (__fds) != (__SIZE_TYPE__) -1)
@ -54,7 +55,8 @@ extern int __REDIRECT (__ppoll_alias, (struct pollfd *__fds, nfds_t __nfds,
const __sigset_t *__ss), ppoll);
extern int __ppoll_chk (struct pollfd *__fds, nfds_t __nfds,
const struct timespec *__timeout,
const __sigset_t *__ss, __SIZE_TYPE__ __fdslen);
const __sigset_t *__ss, __SIZE_TYPE__ __fdslen)
__attr_access ((__write_only__, 1, 2));
extern int __REDIRECT (__ppoll_chk_warn, (struct pollfd *__fds, nfds_t __nfds,
const struct timespec *__timeout,
const __sigset_t *__ss,
@ -62,7 +64,7 @@ extern int __REDIRECT (__ppoll_chk_warn, (struct pollfd *__fds, nfds_t __nfds,
__ppoll_chk)
__warnattr ("ppoll called with fds buffer too small file nfds entries");
__fortify_function int
__fortify_function __attr_access ((__write_only__, 1, 2)) int
ppoll (struct pollfd *__fds, nfds_t __nfds, const struct timespec *__timeout,
const __sigset_t *__ss)
{

View File

@ -51,7 +51,8 @@ __BEGIN_DECLS
This function is a cancellation point and therefore not marked with
__THROW. */
extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);
extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout)
__attr_access ((__write_only__, 1, 2));
#ifdef __USE_GNU
/* Like poll, but before waiting the threads signal mask is replaced
@ -62,7 +63,9 @@ extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);
__THROW. */
extern int ppoll (struct pollfd *__fds, nfds_t __nfds,
const struct timespec *__timeout,
const __sigset_t *__ss);
const __sigset_t *__ss)
__attr_access ((__write_only__, 1, 2));
#endif
__END_DECLS

View File

@ -184,12 +184,12 @@ extern FILE *tmpfile64 (void) __wur;
#endif
/* Generate a temporary filename. */
extern char *tmpnam (char *__s) __THROW __wur;
extern char *tmpnam (char[L_tmpnam]) __THROW __wur;
#ifdef __USE_MISC
/* This is the reentrant variant of `tmpnam'. The only difference is
that it does not allow S to be NULL. */
extern char *tmpnam_r (char *__s) __THROW __wur;
extern char *tmpnam_r (char __s[L_tmpnam]) __THROW __wur;
#endif
@ -808,13 +808,15 @@ extern int pclose (FILE *__stream);
#ifdef __USE_POSIX
/* Return the name of the controlling terminal. */
extern char *ctermid (char *__s) __THROW;
extern char *ctermid (char *__s) __THROW
__attr_access ((__write_only__, 1));
#endif /* Use POSIX. */
#if (defined __USE_XOPEN && !defined __USE_XOPEN2K) || defined __USE_GNU
/* Return the name of the current user. */
extern char *cuserid (char *__s);
extern char *cuserid (char *__s)
__attr_access ((__write_only__, 1));
#endif /* Use X/Open, but not issue 6. */

View File

@ -39,7 +39,7 @@ __BEGIN_DECLS
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t readv (int __fd, const struct iovec *__iovec, int __count)
__wur;
__wur __attr_access ((__read_only__, 2, 3));
/* Write data pointed by the buffers described by IOVEC, which
is a vector of COUNT 'struct iovec's, to file descriptor FD.
@ -50,7 +50,7 @@ extern ssize_t readv (int __fd, const struct iovec *__iovec, int __count)
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t writev (int __fd, const struct iovec *__iovec, int __count)
__wur;
__wur __attr_access ((__read_only__, 2, 3));
#ifdef __USE_MISC
@ -65,7 +65,8 @@ extern ssize_t writev (int __fd, const struct iovec *__iovec, int __count)
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t preadv (int __fd, const struct iovec *__iovec, int __count,
__off_t __offset) __wur;
__off_t __offset)
__wur __attr_access ((__read_only__, 2, 3));
/* Write data pointed by the buffers described by IOVEC, which is a
vector of COUNT 'struct iovec's, to file descriptor FD at the given
@ -77,16 +78,19 @@ extern ssize_t preadv (int __fd, const struct iovec *__iovec, int __count,
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t pwritev (int __fd, const struct iovec *__iovec, int __count,
__off_t __offset) __wur;
__off_t __offset)
__wur __attr_access ((__read_only__, 2, 3));
# else
# ifdef __REDIRECT
extern ssize_t __REDIRECT (preadv, (int __fd, const struct iovec *__iovec,
int __count, __off64_t __offset),
preadv64) __wur;
preadv64)
__wur __attr_access ((__read_only__, 2, 3));
extern ssize_t __REDIRECT (pwritev, (int __fd, const struct iovec *__iovec,
int __count, __off64_t __offset),
pwritev64) __wur;
pwritev64)
__wur __attr_access ((__read_only__, 2, 3));
# else
# define preadv preadv64
# define pwritev pwritev64
@ -104,7 +108,8 @@ extern ssize_t __REDIRECT (pwritev, (int __fd, const struct iovec *__iovec,
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t preadv64 (int __fd, const struct iovec *__iovec, int __count,
__off64_t __offset) __wur;
__off64_t __offset)
__wur __attr_access ((__read_only__, 2, 3));
/* Write data pointed by the buffers described by IOVEC, which is a
vector of COUNT 'struct iovec's, to file descriptor FD at the given
@ -116,7 +121,8 @@ extern ssize_t preadv64 (int __fd, const struct iovec *__iovec, int __count,
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t pwritev64 (int __fd, const struct iovec *__iovec, int __count,
__off64_t __offset) __wur;
__off64_t __offset)
__wur __attr_access ((__read_only__, 2, 3));
# endif
#endif /* Use misc. */
@ -125,7 +131,8 @@ extern ssize_t pwritev64 (int __fd, const struct iovec *__iovec, int __count,
# ifndef __USE_FILE_OFFSET64
/* Same as preadv but with an additional flag argumenti defined at uio.h. */
extern ssize_t preadv2 (int __fp, const struct iovec *__iovec, int __count,
__off_t __offset, int ___flags) __wur;
__off_t __offset, int ___flags)
__wur __attr_access ((__read_only__, 2, 3));
/* Same as preadv but with an additional flag argument defined at uio.h. */
extern ssize_t pwritev2 (int __fd, const struct iovec *__iodev, int __count,
@ -136,11 +143,13 @@ extern ssize_t pwritev2 (int __fd, const struct iovec *__iodev, int __count,
extern ssize_t __REDIRECT (pwritev2, (int __fd, const struct iovec *__iovec,
int __count, __off64_t __offset,
int __flags),
pwritev64v2) __wur;
pwritev64v2)
__wur __attr_access ((__read_only__, 2, 3));
extern ssize_t __REDIRECT (preadv2, (int __fd, const struct iovec *__iovec,
int __count, __off64_t __offset,
int __flags),
preadv64v2) __wur;
preadv64v2)
__wur __attr_access ((__read_only__, 2, 3));
# else
# define preadv2 preadv64v2
# define pwritev2 pwritev64v2
@ -151,12 +160,14 @@ extern ssize_t __REDIRECT (preadv2, (int __fd, const struct iovec *__iovec,
/* Same as preadv but with an additional flag argumenti defined at uio.h. */
extern ssize_t preadv64v2 (int __fp, const struct iovec *__iovec,
int __count, __off64_t __offset,
int ___flags) __wur;
int ___flags)
__wur __attr_access ((__read_only__, 2, 3));
/* Same as preadv but with an additional flag argument defined at uio.h. */
extern ssize_t pwritev64v2 (int __fd, const struct iovec *__iodev,
int __count, __off64_t __offset,
int __flags) __wur;
int __flags)
__wur __attr_access ((__read_only__, 2, 3));
# endif
#endif /* Use GNU. */

View File

@ -747,7 +747,8 @@ write_output (int fd)
header->valstrlen = valstrlen;
size_t filled_dbs = 0;
struct iovec iov[2 + ndatabases * 3];
size_t iov_nelts = 2 + ndatabases * 3;
struct iovec iov[iov_nelts];
iov[0].iov_base = header;
iov[0].iov_len = file_offset;
@ -791,7 +792,9 @@ write_output (int fd)
+ nhashentries_total * sizeof (stridx_t)));
header->allocate = file_offset;
if (writev (fd, iov, 2 + ndatabases * 3) != keydataoffset)
/* Help GCC 10 see iov_nelts doesn't overflow the writev argument. */
assert (iov_nelts <= INT_MAX);
if (writev (fd, iov, iov_nelts) != keydataoffset)
{
error (0, errno, gettext ("failed to write new database file"));
return EXIT_FAILURE;

View File

@ -105,7 +105,7 @@ do_test (void)
/* 新処圭新, \xb7\xbd here really matches 圭,
* this is a reproducer of bug-regex25 */
e = re_search (&r, "\xbf\xb7\xbd\xe8\xb7\xbd\xbf\xb7",
10, 0, 10, &s);
9, 0, 9, &s);
if (e != 4)
{
printf ("bug-regex33.7: no match or false match: re_search() returned %d, should return 4\n", e);

View File

@ -536,7 +536,8 @@ extern reg_syntax_t re_set_syntax (reg_syntax_t __syntax);
'regcomp', with a malloc'ed value, or set to NULL before calling
'regfree'. */
extern const char *re_compile_pattern (const char *__pattern, size_t __length,
struct re_pattern_buffer *__buffer);
struct re_pattern_buffer *__buffer)
__attr_access ((__read_only__, 1, 2));
/* Compile a fastmap for the compiled pattern in BUFFER; used to
@ -553,7 +554,8 @@ extern int re_compile_fastmap (struct re_pattern_buffer *__buffer);
extern regoff_t re_search (struct re_pattern_buffer *__buffer,
const char *__String, regoff_t __length,
regoff_t __start, regoff_t __range,
struct re_registers *__regs);
struct re_registers *__regs)
__attr_access ((__read_only__, 2, 3));
/* Like 're_search', but search in the concatenation of STRING1 and
@ -563,14 +565,17 @@ extern regoff_t re_search_2 (struct re_pattern_buffer *__buffer,
const char *__string2, regoff_t __length2,
regoff_t __start, regoff_t __range,
struct re_registers *__regs,
regoff_t __stop);
regoff_t __stop)
__attr_access ((__read_only__, 2, 3))
__attr_access ((__read_only__, 4, 5));
/* Like 're_search', but return how many characters in STRING the regexp
in BUFFER matched, starting at position START. */
extern regoff_t re_match (struct re_pattern_buffer *__buffer,
const char *__String, regoff_t __length,
regoff_t __start, struct re_registers *__regs);
regoff_t __start, struct re_registers *__regs)
__attr_access ((__read_only__, 2, 3));
/* Relates to 're_match' as 're_search_2' relates to 're_search'. */
@ -578,7 +583,9 @@ extern regoff_t re_match_2 (struct re_pattern_buffer *__buffer,
const char *__string1, regoff_t __length1,
const char *__string2, regoff_t __length2,
regoff_t __start, struct re_registers *__regs,
regoff_t __stop);
regoff_t __stop)
__attr_access ((__read_only__, 2, 3))
__attr_access ((__read_only__, 4, 5));
/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
@ -648,10 +655,12 @@ extern int regcomp (regex_t *_Restrict_ __preg,
extern int regexec (const regex_t *_Restrict_ __preg,
const char *_Restrict_ __String, size_t __nmatch,
regmatch_t __pmatch[_Restrict_arr_],
int __eflags);
int __eflags)
__attr_access ((__write_only__, 4, 3));
extern size_t regerror (int __errcode, const regex_t *_Restrict_ __preg,
char *_Restrict_ __errbuf, size_t __errbuf_size);
char *_Restrict_ __errbuf, size_t __errbuf_size)
__attr_access ((__write_only__, 3, 4));
extern void regfree (regex_t *__preg);

View File

@ -139,20 +139,23 @@ extern struct passwd *getpwnam (const char *__name) __nonnull ((1));
extern int getpwent_r (struct passwd *__restrict __resultbuf,
char *__restrict __buffer, size_t __buflen,
struct passwd **__restrict __result)
__nonnull ((1, 2, 4));
__nonnull ((1, 2, 4))
__attr_access ((__write_only__, 2, 3));
# endif
extern int getpwuid_r (__uid_t __uid,
struct passwd *__restrict __resultbuf,
char *__restrict __buffer, size_t __buflen,
struct passwd **__restrict __result)
__nonnull ((2, 3, 5));
__nonnull ((2, 3, 5))
__attr_access ((__write_only__, 3, 4));
extern int getpwnam_r (const char *__restrict __name,
struct passwd *__restrict __resultbuf,
char *__restrict __buffer, size_t __buflen,
struct passwd **__restrict __result)
__nonnull ((1, 2, 3, 5));
__nonnull ((1, 2, 3, 5))
__attr_access ((__write_only__, 3, 4));
# ifdef __USE_MISC
@ -167,7 +170,8 @@ extern int fgetpwent_r (FILE *__restrict __stream,
struct passwd *__restrict __resultbuf,
char *__restrict __buffer, size_t __buflen,
struct passwd **__restrict __result)
__nonnull ((1, 2, 3, 5));
__nonnull ((1, 2, 3, 5))
__attr_access ((__write_only__, 3, 4));
# endif
#endif /* POSIX or reentrant */

View File

@ -24,7 +24,7 @@ static char tmpnam_buffer[L_tmpnam];
This function is *not* thread safe! */
char *
tmpnam (char *s)
tmpnam (char s[L_tmpnam])
{
/* By using two buffers we manage to be thread safe in the case
where S != NULL. */

View File

@ -20,7 +20,7 @@
/* Generate a unique filename in P_tmpdir. If S is NULL return NULL.
This makes this function thread safe. */
char *
tmpnam_r (char *s)
tmpnam_r (char s[L_tmpnam])
{
if (s == NULL)
return NULL;

View File

@ -37,7 +37,8 @@ __BEGIN_DECLS
/* Formatting a monetary value according to the current locale. */
extern ssize_t strfmon (char *__restrict __s, size_t __maxsize,
const char *__restrict __format, ...)
__THROW __attribute_format_strfmon__ (3, 4);
__THROW __attribute_format_strfmon__ (3, 4)
__attr_access ((__write_only__, 1, 2));
#ifdef __USE_XOPEN2K8
/* POSIX.1-2008 extended locale interface (see locale.h). */
@ -47,7 +48,8 @@ extern ssize_t strfmon (char *__restrict __s, size_t __maxsize,
extern ssize_t strfmon_l (char *__restrict __s, size_t __maxsize,
locale_t __loc,
const char *__restrict __format, ...)
__THROW __attribute_format_strfmon__ (4, 5);
__THROW __attribute_format_strfmon__ (4, 5)
__attr_access ((__write_only__, 1, 2));
#endif
#include <bits/floatn.h>

View File

@ -191,7 +191,9 @@ __BEGIN_DECLS
/* Convert an interface name to an index, and vice versa. */
extern unsigned int if_nametoindex (const char *__ifname) __THROW;
extern char *if_indextoname (unsigned int __ifindex, char *__ifname) __THROW;
extern char *if_indextoname (unsigned int __ifindex,
char __ifname[IF_NAMESIZE]) __THROW
__attr_access ((__write_only__, 2));
/* Return a list of all interfaces and their indices. */
extern struct if_nameindex *if_nameindex (void) __THROW;

View File

@ -166,7 +166,7 @@ libc_hidden_weak (if_nameindex)
IFNAME (which has space for at least IFNAMSIZ characters). Return
IFNAME, or NULL on error. */
char *
__if_indextoname (unsigned int ifindex, char *ifname)
__if_indextoname (unsigned int ifindex, char ifname[IF_NAMESIZE])
{
struct ifreq ifr;
int fd = __opensock ();

View File

@ -215,7 +215,7 @@ libc_hidden_weak (if_nameindex)
char *
__if_indextoname (unsigned int ifindex, char *ifname)
__if_indextoname (unsigned int ifindex, char ifname[IF_NAMESIZE])
{
/* We may be able to do the conversion directly, rather than searching a
list. This ioctl is not present in kernels before version 2.1.50. */

View File

@ -44,6 +44,7 @@
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libc-diag.h>
/* This is not an exhaustive test: only system calls that can be
persuaded to fail with a consistent error code and no side effects
@ -171,7 +172,18 @@ do_test (void)
allocation. */
fails |= test_wrp2 (LIST (EINVAL, ENOMEM), mlock, (void *) -1, 1);
fails |= test_wrp (EINVAL, nanosleep, &ts, &ts);
DIAG_POP_NEEDS_COMMENT;
#if __GNUC_PREREQ (9, 0)
/* Suppress valid GCC warning:
'poll' specified size 18446744073709551608 exceeds maximum object size
*/
DIAG_IGNORE_NEEDS_COMMENT (9, "-Wstringop-overflow=");
#endif
fails |= test_wrp (EINVAL, poll, &pollfd, -1, 0);
DIAG_POP_NEEDS_COMMENT;
/* quotactl returns ENOSYS for kernels not configured with
CONFIG_QUOTA, and may return EPERM if called within certain types
of containers. Linux 5.4 added additional argument validation