From 524d6a2ef5cb6f367d4f14d5ab5a35df4c7fe08f Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 31 Dec 2009 11:53:33 -0700 Subject: [PATCH] stdio: warn on suspicious uses Using gets is almost ALWAYS wrong (it is extremely rare that you have full control over stdin). POSIX 2008 marked it as obsolete, even though C89 requires it. Attach a warning to remind developers. Add a comment to sprintf explaining why it does not get this treatment. Improve the warnings for fseek/fseeko (and ftell/ftello), with comments justifying our position. Some of our unit tests never use large files, so rather than drag in a dependency on fseeko, they should be the first compilation units to use _GL_NO_LARGE_FILES. * modules/stdio (Depends-on): Add warn-on-use. (Makefile.am): Provide new substitutions. * m4/stdio_h.m4 (gl_STDIO_H): Check for inline, ftello, and fseeko. * lib/stdio.in.h (gets): Always warn on use. (fseek, ftell): Adjust when warnings are issued, and honor _GL_NO_LARGE_FILES as a way to silence the warning. * tests/test-fpurge.c [!GNULIB_FSEEK]: Use new means to squelch any warning about large file offsets. * tests/test-freadable.c [!GNULIB_FSEEK]: Likewise. * tests/test-freading.c [!GNULIB_FSEEK]: Likewise. * tests/test-fseeko.c [!GNULIB_FSEEK]: Likewise. * tests/test-ftell.c [!GNULIB_FSEEK]: Likewise. * tests/test-ftello.c [!GNULIB_FSEEK]: Likewise. * tests/test-fwritable.c [!GNULIB_FSEEK]: Likewise. * tests/test-fwriting.c [!GNULIB_FSEEK]: Likewise. * tests/test-getopt.c [!GNULIB_FTELL]: Likewise. Signed-off-by: Eric Blake --- ChangeLog | 19 ++++++ lib/stdio.in.h | 169 +++++++++++++++++++++++++++++++++---------------- m4/stdio_h.m4 | 9 ++- modules/stdio | 4 +- tests/test-fpurge.c | 9 +-- tests/test-freadable.c | 9 +-- tests/test-freading.c | 9 +-- tests/test-fseeko.c | 6 +- tests/test-ftell.c | 9 +-- tests/test-ftello.c | 9 +-- tests/test-fwritable.c | 9 +-- tests/test-fwriting.c | 9 +-- tests/test-getopt.c | 5 +- 13 files changed, 168 insertions(+), 107 deletions(-) diff --git a/ChangeLog b/ChangeLog index 08a43904c..c898b9a09 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,24 @@ 2010-01-11 Eric Blake + stdio: warn on suspicious uses + * modules/stdio (Depends-on): Add warn-on-use. + (Makefile.am): Provide new substitutions. + * m4/stdio_h.m4 (gl_STDIO_H): Check for inline, ftello, and + fseeko. + * lib/stdio.in.h (gets): Always warn on use. + (fseek, ftell): Adjust when warnings are issued, and honor + _GL_NO_LARGE_FILES as a way to silence the warning. + * tests/test-fpurge.c [!GNULIB_FSEEK]: Use new means to squelch + any warning about large file offsets. + * tests/test-freadable.c [!GNULIB_FSEEK]: Likewise. + * tests/test-freading.c [!GNULIB_FSEEK]: Likewise. + * tests/test-fseeko.c [!GNULIB_FSEEK]: Likewise. + * tests/test-ftell.c [!GNULIB_FSEEK]: Likewise. + * tests/test-ftello.c [!GNULIB_FSEEK]: Likewise. + * tests/test-fwritable.c [!GNULIB_FSEEK]: Likewise. + * tests/test-fwriting.c [!GNULIB_FSEEK]: Likewise. + * tests/test-getopt.c [!GNULIB_FTELL]: Likewise. + warn-on-use: new module * modules/warn-on-use: New file. * build-aux/warn-on-use.h: Likewise. diff --git a/lib/stdio.in.h b/lib/stdio.in.h index 9e1fac38e..3f0ddae6b 100644 --- a/lib/stdio.in.h +++ b/lib/stdio.in.h @@ -62,6 +62,8 @@ /* The definition of _GL_ARG_NONNULL is copied here. */ +/* The definition of _GL_WARN_ON_USE is copied here. */ + #ifdef __cplusplus extern "C" { @@ -118,6 +120,12 @@ extern int fclose (FILE *stream) _GL_ARG_NONNULL ((1)); fflush (f)) #endif +/* It is very rare that the developer ever has full control of stdin, + so any use of gets warrants an unconditional warning. Assume it is + always declared, since it is required by C89. */ +#undef gets +_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead"); + #if @GNULIB_FOPEN@ # if @REPLACE_FOPEN@ # undef fopen @@ -202,92 +210,136 @@ extern FILE * freopen (const char *filename, const char *mode, FILE *stream) freopen (f, m, s)) #endif -#if @GNULIB_FSEEK@ && @REPLACE_FSEEK@ -extern int rpl_fseek (FILE *fp, long offset, int whence) _GL_ARG_NONNULL ((1)); -# undef fseek -# if defined GNULIB_POSIXCHECK -# define fseek(f,o,w) \ - (GL_LINK_WARNING ("fseek cannot handle files larger than 4 GB " \ - "on 32-bit platforms - " \ - "use fseeko function for handling of large files"), \ - rpl_fseek (f, o, w)) -# else +/* Set up the following warnings, based on which modules are in use. + GNU Coding Standards discourage the use of fseek, since it imposes + an arbitrary limitation on some 32-bit hosts. Remember that the + fseek module depends on the fseeko module, so we only have three + cases to consider: + + 1. The developer is not using either module. Issue a warning under + GNULIB_POSIXCHECK for both functions, to remind them that both + functions have bugs on some systems. _GL_NO_LARGE_FILES has no + impact on this warning. + + 2. The developer is using both modules. They may be unaware of the + arbitrary limitations of fseek, so issue a warning under + GNULIB_POSIXCHECK. On the other hand, they may be using both + modules intentionally, so the developer can define + _GL_NO_LARGE_FILES in the compilation units where the use of fseek + is safe, to silence the warning. + + 3. The developer is using the fseeko module, but not fseek. Gnulib + guarantees that fseek will still work around platform bugs in that + case, but we presume that the developer is aware of the pitfalls of + fseek and was trying to avoid it, so issue a warning even when + GNULIB_POSIXCHECK is undefined. Again, _GL_NO_LARGE_FILES can be + defined to silence the warning in particular compilation units. + + Most gnulib clients that perform stream operations should fall into + category three. */ + +#if @GNULIB_FSEEK@ +# if defined GNULIB_POSIXCHECK && !defined _GL_NO_LARGE_FILES +# define _GL_FSEEK_WARN /* Category 2, above. */ +# undef fseek +# endif +# if @REPLACE_FSEEK@ +# undef fseek # define fseek rpl_fseek -# endif -#elif defined GNULIB_POSIXCHECK -# ifndef fseek -# define fseek(f,o,w) \ - (GL_LINK_WARNING ("fseek cannot handle files larger than 4 GB " \ - "on 32-bit platforms - " \ - "use fseeko function for handling of large files"), \ - fseek (f, o, w)) +extern int fseek (FILE *fp, long offset, int whence) _GL_ARG_NONNULL ((1)); # endif #endif #if @GNULIB_FSEEKO@ +# if !@GNULIB_FSEEK@ && !defined _GL_NO_LARGE_FILES +# define _GL_FSEEK_WARN /* Category 3, above. */ +# undef fseek +# endif # if @REPLACE_FSEEKO@ /* Provide fseek, fseeko functions that are aware of a preceding fflush(), and which detect pipes. */ +# undef fseeko # define fseeko rpl_fseeko extern int fseeko (FILE *fp, off_t offset, int whence) _GL_ARG_NONNULL ((1)); # if !@GNULIB_FSEEK@ # undef fseek -# define fseek(f,o,w) \ - (GL_LINK_WARNING ("fseek cannot handle files larger than 4 GB " \ - "on 32-bit platforms - " \ - "use fseeko function for handling of large files"), \ - fseeko (f, o, w)) +# define fseek rpl_fseek +static inline int _GL_ARG_NONNULL ((1)) +rpl_fseek (FILE *fp, long offset, int whence) +{ + return fseeko (fp, offset, whence); +} # endif # endif #elif defined GNULIB_POSIXCHECK +# define _GL_FSEEK_WARN /* Category 1, above. */ +# undef fseek # undef fseeko -# define fseeko(f,o,w) \ - (GL_LINK_WARNING ("fseeko is unportable - " \ - "use gnulib module fseeko for portability"), \ - fseeko (f, o, w)) +# if HAVE_RAW_DECL_FSEEKO +_GL_WARN_ON_USE (fseeko, "fseeko is unportable - " + "use gnulib module fseeko for portability"); +# endif #endif -#if @GNULIB_FTELL@ && @REPLACE_FTELL@ -extern long rpl_ftell (FILE *fp) _GL_ARG_NONNULL ((1)); -# undef ftell -# if GNULIB_POSIXCHECK -# define ftell(f) \ - (GL_LINK_WARNING ("ftell cannot handle files larger than 4 GB " \ - "on 32-bit platforms - " \ - "use ftello function for handling of large files"), \ - rpl_ftell (f)) -# else -# define ftell rpl_ftell +#ifdef _GL_FSEEK_WARN +# undef _GL_FSEEK_WARN +/* Here, either fseek is undefined (but C89 guarantees that it is + declared), or it is defined as rpl_fseek (declared above). */ +_GL_WARN_ON_USE (fseek, "fseek cannot handle files larger than 4 GB " + "on 32-bit platforms - " + "use fseeko function for handling of large files"); +#endif + +/* See the comments on fseek/fseeko. */ + +#if @GNULIB_FTELL@ +# if defined GNULIB_POSIXCHECK && !defined _GL_NO_LARGE_FILES +# define _GL_FTELL_WARN /* Category 2, above. */ +# undef ftell # endif -#elif defined GNULIB_POSIXCHECK -# ifndef ftell -# define ftell(f) \ - (GL_LINK_WARNING ("ftell cannot handle files larger than 4 GB " \ - "on 32-bit platforms - " \ - "use ftello function for handling of large files"), \ - ftell (f)) +# if && @REPLACE_FTELL@ +# undef ftell +# define ftell rpl_ftell +extern long ftell (FILE *fp) _GL_ARG_NONNULL ((1)); # endif #endif #if @GNULIB_FTELLO@ +# if !@GNULIB_FTELL@ && !defined _GL_NO_LARGE_FILES +# define _GL_FTELL_WARN /* Category 3, above. */ +# undef ftell +# endif # if @REPLACE_FTELLO@ +# undef ftello # define ftello rpl_ftello extern off_t ftello (FILE *fp) _GL_ARG_NONNULL ((1)); # if !@GNULIB_FTELL@ # undef ftell -# define ftell(f) \ - (GL_LINK_WARNING ("ftell cannot handle files larger than 4 GB " \ - "on 32-bit platforms - " \ - "use ftello function for handling of large files"), \ - ftello (f)) +# define ftell rpl_ftell +static inline long _GL_ARG_NONNULL ((1)) +rpl_ftell (FILE *f) +{ + return ftello (f); +} # endif # endif #elif defined GNULIB_POSIXCHECK +# define _GL_FTELL_WARN /* Category 1, above. */ +# undef ftell # undef ftello -# define ftello(f) \ - (GL_LINK_WARNING ("ftello is unportable - " \ - "use gnulib module ftello for portability"), \ - ftello (f)) +# if HAVE_RAW_DECL_FTELLO +_GL_WARN_ON_USE (ftello, "ftello is unportable - " + "use gnulib module ftello for portability"); +# endif +#endif + +#ifdef _GL_FTELL_WARN +# undef _GL_FTELL_WARN +/* Here, either ftell is undefined (but C89 guarantees that it is + declared), or it is defined as rpl_ftell (declared above). */ +_GL_WARN_ON_USE (ftell, "ftell cannot handle files larger than 4 GB " + "on 32-bit platforms - " + "use ftello function for handling of large files"); #endif #if @GNULIB_FWRITE@ && @REPLACE_STDIO_WRITE_FUNCS@ && @GNULIB_STDIO_H_SIGPIPE@ @@ -500,6 +552,15 @@ extern int snprintf (char *str, size_t size, const char *format, ...) snprintf) #endif +/* Some people would argue that sprintf should be handled like gets + (for example, OpenBSD issues a link warning for both functions), + since both can cause security holes due to buffer overruns. + However, we believe that sprintf can be used safely, and is more + efficient than snprintf in those safe cases; and as proof of our + belief, we use sprintf in several gnulib modules. So this header + intentionally avoids adding a warning to sprintf except when + GNULIB_POSIXCHECK is defined. */ + #if @GNULIB_SPRINTF_POSIX@ # if @REPLACE_SPRINTF@ # define sprintf rpl_sprintf diff --git a/m4/stdio_h.m4 b/m4/stdio_h.m4 index 7e596923a..be79896d5 100644 --- a/m4/stdio_h.m4 +++ b/m4/stdio_h.m4 @@ -1,4 +1,4 @@ -# stdio_h.m4 serial 22 +# stdio_h.m4 serial 23 dnl Copyright (C) 2007-2010 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -7,6 +7,7 @@ dnl with or without modifications, as long as this notice is preserved. AC_DEFUN([gl_STDIO_H], [ AC_REQUIRE([gl_STDIO_H_DEFAULTS]) + AC_REQUIRE([AC_C_INLINE]) gl_CHECK_NEXT_HEADERS([stdio.h]) dnl No need to create extra modules for these functions. Everyone who uses dnl likely needs them. @@ -30,6 +31,12 @@ AC_DEFUN([gl_STDIO_H], AC_LIBOBJ([stdio-write]) fi ]) + + dnl Check for declarations of anything we want to poison if the + dnl corresponding gnulib module is not in use, and which is not + dnl guaranteed by C89. + gl_WARN_ON_USE_PREPARE([[#include + ]], [fseeko ftello]) ]) AC_DEFUN([gl_STDIO_MODULE_INDICATOR], diff --git a/modules/stdio b/modules/stdio index eca35813c..6da339bf3 100644 --- a/modules/stdio +++ b/modules/stdio @@ -12,6 +12,7 @@ link-warning arg-nonnull raise stddef +warn-on-use configure.ac: gl_STDIO_H @@ -21,7 +22,7 @@ BUILT_SOURCES += stdio.h # We need the following in order to create when the system # doesn't have one that works with the given compiler. -stdio.h: stdio.in.h $(LINK_WARNING_H) $(ARG_NONNULL_H) +stdio.h: stdio.in.h $(LINK_WARNING_H) $(WARN_ON_USE_H) $(ARG_NONNULL_H) $(AM_V_GEN)rm -f $@-t $@ && \ { echo '/* DO NOT EDIT! GENERATED AUTOMATICALLY! */' && \ sed -e 's|@''INCLUDE_NEXT''@|$(INCLUDE_NEXT)|g' \ @@ -107,6 +108,7 @@ stdio.h: stdio.in.h $(LINK_WARNING_H) $(ARG_NONNULL_H) -e 's|@''REPLACE_VSPRINTF''@|$(REPLACE_VSPRINTF)|g' \ -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \ -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \ + -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \ < $(srcdir)/stdio.in.h; \ } > $@-t && \ mv $@-t $@ diff --git a/tests/test-fpurge.c b/tests/test-fpurge.c index 15eff6505..89fe8c0ca 100644 --- a/tests/test-fpurge.c +++ b/tests/test-fpurge.c @@ -18,18 +18,15 @@ #include +/* None of the files accessed by this test are large, so disable the + fseek link warning if we are not using the gnulib fseek module. */ +#define _GL_NO_LARGE_FILES #include #include #include "macros.h" -/* None of the files accessed by this test are large, so disable the - fseek link warning if we are not using the gnulib fseek module. */ -#if !GNULIB_FSEEK -# undef fseek -#endif - #define TESTFILE "t-fpurge.tmp" int diff --git a/tests/test-freadable.c b/tests/test-freadable.c index 8f337d88e..2aa6743c5 100644 --- a/tests/test-freadable.c +++ b/tests/test-freadable.c @@ -18,18 +18,15 @@ #include +/* None of the files accessed by this test are large, so disable the + fseek link warning if we are not using the gnulib fseek module. */ +#define _GL_NO_LARGE_FILES #include "freadable.h" #include #include "macros.h" -/* None of the files accessed by this test are large, so disable the - fseek link warning if we are not using the gnulib fseek module. */ -#if !GNULIB_FSEEK -# undef fseek -#endif - #define TESTFILE "t-freadable.tmp" int diff --git a/tests/test-freading.c b/tests/test-freading.c index c4c00f457..fee82284d 100644 --- a/tests/test-freading.c +++ b/tests/test-freading.c @@ -18,18 +18,15 @@ #include +/* None of the files accessed by this test are large, so disable the + fseek link warning if we are not using the gnulib fseek module. */ +#define _GL_NO_LARGE_FILES #include "freading.h" #include #include "macros.h" -/* None of the files accessed by this test are large, so disable the - fseek link warning if we are not using the gnulib fseek module. */ -#if !GNULIB_FSEEK -# undef fseek -#endif - #define TESTFILE "t-freading.tmp" int diff --git a/tests/test-fseeko.c b/tests/test-fseeko.c index e8c66dfe2..be2a78dce 100644 --- a/tests/test-fseeko.c +++ b/tests/test-fseeko.c @@ -20,11 +20,7 @@ /* None of the files accessed by this test are large, so disable the fseek link warning if we are not using the gnulib fseek module. */ -#if !GNULIB_FSEEK -# undef GL_LINK_WARNING -# define GL_LINK_WARNING(ignored) ((void) 0) -#endif - +#define _GL_NO_LARGE_FILES #include #include "signature.h" diff --git a/tests/test-ftell.c b/tests/test-ftell.c index 42eff9cdf..6ad8e0a99 100644 --- a/tests/test-ftell.c +++ b/tests/test-ftell.c @@ -18,6 +18,9 @@ #include +/* None of the files accessed by this test are large, so disable the + fseek link warning if we are not using the gnulib fseek module. */ +#define _GL_NO_LARGE_FILES #include #include "signature.h" @@ -26,12 +29,6 @@ SIGNATURE_CHECK (ftell, long, (FILE *)); #include "binary-io.h" #include "macros.h" -/* None of the files accessed by this test are large, so disable the - fseek link warning if we are not using the gnulib fseek module. */ -#if !GNULIB_FSEEK -# undef fseek -#endif - #ifndef FUNC_UNGETC_BROKEN # define FUNC_UNGETC_BROKEN 0 #endif diff --git a/tests/test-ftello.c b/tests/test-ftello.c index 826df1036..5fae570f2 100644 --- a/tests/test-ftello.c +++ b/tests/test-ftello.c @@ -18,6 +18,9 @@ #include +/* None of the files accessed by this test are large, so disable the + fseek link warning if we are not using the gnulib fseek module. */ +#define _GL_NO_LARGE_FILES #include #include "signature.h" @@ -26,12 +29,6 @@ SIGNATURE_CHECK (ftello, off_t, (FILE *)); #include "binary-io.h" #include "macros.h" -/* None of the files accessed by this test are large, so disable the - fseek link warning if we are not using the gnulib fseek module. */ -#if !GNULIB_FSEEK -# undef fseek -#endif - #ifndef FUNC_UNGETC_BROKEN # define FUNC_UNGETC_BROKEN 0 #endif diff --git a/tests/test-fwritable.c b/tests/test-fwritable.c index 2d696e97f..ab6ca95fd 100644 --- a/tests/test-fwritable.c +++ b/tests/test-fwritable.c @@ -18,18 +18,15 @@ #include +/* None of the files accessed by this test are large, so disable the + fseek link warning if we are not using the gnulib fseek module. */ +#define _GL_NO_LARGE_FILES #include "fwritable.h" #include #include "macros.h" -/* None of the files accessed by this test are large, so disable the - fseek link warning if we are not using the gnulib fseek module. */ -#if !GNULIB_FSEEK -# undef fseek -#endif - #define TESTFILE "t-fwritable.tmp" int diff --git a/tests/test-fwriting.c b/tests/test-fwriting.c index ccc302e1c..d44d0034a 100644 --- a/tests/test-fwriting.c +++ b/tests/test-fwriting.c @@ -18,18 +18,15 @@ #include +/* None of the files accessed by this test are large, so disable the + fseek link warning if we are not using the gnulib fseek module. */ +#define _GL_NO_LARGE_FILES #include "fwriting.h" #include #include "macros.h" -/* None of the files accessed by this test are large, so disable the - fseek link warning if we are not using the gnulib fseek module. */ -#if !GNULIB_FSEEK -# undef fseek -#endif - #define TESTFILE "t-fwriting.tmp" int diff --git a/tests/test-getopt.c b/tests/test-getopt.c index fe822ed16..6bcb8e6ae 100644 --- a/tests/test-getopt.c +++ b/tests/test-getopt.c @@ -20,10 +20,7 @@ /* None of the files accessed by this test are large, so disable the ftell link warning if we are not using the gnulib ftell module. */ -#if !GNULIB_FTELL -# undef GL_LINK_WARNING -# define GL_LINK_WARNING(ignored) ((void) 0) -#endif +#define _GL_NO_LARGE_FILES #if GNULIB_GETOPT_GNU # include -- 2.11.0