From ee92e7bafb52d267f52e4c48edada5f279bcf591 Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Thu, 8 Dec 2016 00:01:33 +0000 Subject: [PATCH] PR c/53562 - Add -Werror= support for -D_FORTIFY_SOURCE / __builtin___memcpy_chk PR c/53562 - Add -Werror= support for -D_FORTIFY_SOURCE / __builtin___memcpy_chk PR middle-end/77784 - duplicate warning for snprintf when n > object size PR middle-end/78149 - missing warning on strncpy buffer overflow due to an excessive bound PR middle-end/78138 - missing warnings on buffer overflow with non-constant source length gcc/c-family/ChangeLog: PR c/53562 PR middle-end/77784 PR middle-end/78149 PR middle-end/78138 * c.opt (-Wstringop-overflow): New option. gcc/ChangeLog: PR middle-end/77784 PR middle-end/78149 PR middle-end/78138 * builtins.c (expand_builtin_strcat, expand_builtin_strncat): New functions. (compute_dest_size, get_size_range, check_sizes, check_strncat_sizes) (check_memop_sizes): Same. (expand_builtin_memcpy): Call check memop_sizes. (expand_builtin_mempcpy): Same. (expand_builtin_memset): Same, (expand_builtin_bzero): Same. (expand_builtin_memory_chk): Call check_sizes. (expand_builtin_strcpy): Same. (expand_builtin_strncpy): Same. (maybe_emit_sprintf_chk_warning): Same. (expand_builtin): Handle strcat and strncat. (fini_object_sizes): Reset pointers. (compute_object_size): New function. * gimple-ssa-sprintf.c (pass_sprintf_length::handle_gimple_call): Avoid issuing warnings also issued during built-in expansion. * doc/invoke.texi (Warning Options): Document -Wstringop-overflow. gcc/testsuite/ChangeLog: PR middle-end/77784 PR middle-end/78149 PR middle-end/78138 * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust expected diagnostic. * g++.dg/ext/builtin-object-size3.C (bar): Same. * g++.dg/ext/strncpy-chk1.C: Same. * g++.dg/opt/memcpy1.C: Same. * g++.dg/torture/Wsizeof-pointer-memaccess1.C: Same. * gcc.c-torture/compile/pr55569.c: Disable -Wstringop-overflow. * gcc.dg/Wobjsize-1.c: Adjust expected diagnostic. * gcc.dg/attr-alloc_size.c: Same. * gcc.dg/builtin-stringop-chk-1.c: Adjust expected diagnostic. * gcc.dg/builtin-stringop-chk-2.c: Same. * gcc.dg/builtin-stringop-chk-4.c: New test. * gcc.dg/builtin-strncat-chk-1.c: Adjust expected diagnostic. * gcc.dg/memcpy-2.c: Same. * gcc.dg/pr40340-1.c: Same. * gcc.dg/pr40340-2.c (main): Same. * gcc.dg/pr40340-5.c (main): Same. * gcc.dg/torture/Wsizeof-pointer-memaccess1.c: Same. * gcc.dg/torture/pr71132.c: Disable -Wstringop-overflow. * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Adjust text of expected warning. * gfortran.dg/char_length_3.f90: Prune expected warnings. * gfortran.dg/pr38868.f: Add expected warnings. From-SVN: r243419 --- gcc/ChangeLog | 25 + gcc/builtins.c | 673 +++++++++++++++--- gcc/c-family/ChangeLog | 8 + gcc/c-family/c.opt | 10 + gcc/doc/invoke.texi | 81 +++ gcc/gimple-ssa-sprintf.c | 61 +- gcc/testsuite/ChangeLog | 29 + .../c-c++-common/Wsizeof-pointer-memaccess2.c | 2 +- .../g++.dg/ext/builtin-object-size3.C | 4 +- gcc/testsuite/g++.dg/ext/strncpy-chk1.C | 2 +- gcc/testsuite/g++.dg/opt/memcpy1.C | 5 +- .../torture/Wsizeof-pointer-memaccess1.C | 2 +- .../torture/Wsizeof-pointer-memaccess2.C | 5 +- gcc/testsuite/gcc.c-torture/compile/pr55569.c | 10 +- gcc/testsuite/gcc.dg/Wobjsize-1.c | 2 +- gcc/testsuite/gcc.dg/attr-alloc_size.c | 8 +- gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c | 54 +- gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c | 4 +- gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c | 525 ++++++++++++++ gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c | 260 +++++++ gcc/testsuite/gcc.dg/builtin-stringop-chk-6.c | 112 +++ gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c | 8 +- .../gcc.dg/fstack-protector-strong.c | 2 +- gcc/testsuite/gcc.dg/memcpy-2.c | 2 +- gcc/testsuite/gcc.dg/pr40340-1.c | 2 +- gcc/testsuite/gcc.dg/pr40340-2.c | 2 +- gcc/testsuite/gcc.dg/pr40340-5.c | 2 +- .../torture/Wsizeof-pointer-memaccess1.c | 2 +- gcc/testsuite/gcc.dg/torture/pr71132.c | 5 + .../gcc.dg/tree-ssa/builtin-sprintf-warn-1.c | 16 +- .../gcc.dg/tree-ssa/builtin-sprintf-warn-3.c | 42 +- gcc/testsuite/gfortran.dg/char_length_3.f90 | 3 + gcc/testsuite/gfortran.dg/pr38868.f | 2 +- 33 files changed, 1779 insertions(+), 191 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c create mode 100644 gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c create mode 100644 gcc/testsuite/gcc.dg/builtin-stringop-chk-6.c diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 79d5d6de19b..6372ff43f19 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,28 @@ +2016-12-07 Martin Sebor + + PR middle-end/77784 + PR middle-end/78149 + PR middle-end/78138 + + * builtins.c (expand_builtin_strcat, expand_builtin_strncat): New + functions. + (compute_dest_size, get_size_range, check_sizes, check_strncat_sizes) + (check_memop_sizes): Same. + (expand_builtin_memcpy): Call check memop_sizes. + (expand_builtin_mempcpy): Same. + (expand_builtin_memset): Same, + (expand_builtin_bzero): Same. + (expand_builtin_memory_chk): Call check_sizes. + (expand_builtin_strcpy): Same. + (expand_builtin_strncpy): Same. + (maybe_emit_sprintf_chk_warning): Same. + (expand_builtin): Handle strcat and strncat. + (fini_object_sizes): Reset pointers. + (compute_object_size): New function. + * gimple-ssa-sprintf.c (pass_sprintf_length::handle_gimple_call): + Avoid issuing warnings also issued during built-in expansion. + * doc/invoke.texi (Warning Options): Document -Wstringop-overflow. + 2016-12-07 Michael Meissner PR target/72717 diff --git a/gcc/builtins.c b/gcc/builtins.c index 58ed469fc63..b58056cf334 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -67,7 +67,7 @@ along with GCC; see the file COPYING3. If not see #include "internal-fn.h" #include "case-cfn-macros.h" #include "gimple-fold.h" - +#include "intl.h" struct target_builtins default_target_builtins; #if SWITCHABLE_TARGET @@ -125,9 +125,11 @@ static rtx expand_builtin_mempcpy (tree, rtx, machine_mode); static rtx expand_builtin_mempcpy_with_bounds (tree, rtx, machine_mode); static rtx expand_builtin_mempcpy_args (tree, tree, tree, rtx, machine_mode, int, tree); +static rtx expand_builtin_strcat (tree, rtx); static rtx expand_builtin_strcpy (tree, rtx); static rtx expand_builtin_strcpy_args (tree, tree, rtx); static rtx expand_builtin_stpcpy (tree, rtx, machine_mode); +static rtx expand_builtin_strncat (tree, rtx); static rtx expand_builtin_strncpy (tree, rtx); static rtx builtin_memset_gen_str (void *, HOST_WIDE_INT, machine_mode); static rtx expand_builtin_memset (tree, rtx, machine_mode); @@ -3010,6 +3012,292 @@ expand_builtin_memcpy_args (tree dest, tree src, tree len, rtx target, tree exp) return dest_addr; } +/* Fill the 2-element RANGE array with the minimum and maximum values + EXP is known to have and return true, otherwise null and return + false. */ + +static bool +get_size_range (tree exp, tree range[2]) +{ + if (tree_fits_uhwi_p (exp)) + { + range[0] = range[1] = exp; + return true; + } + + if (TREE_CODE (exp) == SSA_NAME) + { + wide_int min, max; + enum value_range_type range_type = get_range_info (exp, &min, &max); + + if (range_type == VR_RANGE) + { + /* Interpret the bound in the variable's type. */ + range[0] = wide_int_to_tree (TREE_TYPE (exp), min); + range[1] = wide_int_to_tree (TREE_TYPE (exp), max); + return true; + } + else if (range_type == VR_ANTI_RANGE) + { + /* FIXME: Handle anti-ranges. */ + } + } + + range[0] = NULL_TREE; + range[1] = NULL_TREE; + return false; +} + +/* Try to verify that the sizes and lengths of the arguments to a string + manipulation function given by EXP are within valid bounds and that + the operation does not lead to buffer overflow. Arguments other than + EXP may be null. When non-null, the arguments have the following + meaning: + SIZE is the user-supplied size argument to the function (such as in + memcpy(d, s, SIZE) or strncpy(d, s, SIZE). It specifies the exact + number of bytes to write. + MAXLEN is the user-supplied bound on the length of the source sequence + (such as in strncat(d, s, N). It specifies the upper limit on the number + of bytes to write. + STR is the source string (such as in strcpy(d, s)) when the epxression + EXP is a string function call (as opposed to a memory call like memcpy). + As an exception, STR can also be an integer denoting the precomputed + length of the source string. + OBJSIZE is the size of the destination object specified by the last + argument to the _chk builtins, typically resulting from the expansion + of __builtin_object_size (such as in __builtin___strcpy_chk(d, s, + OBJSIZE). + + When SIZE is null LEN is checked to verify that it doesn't exceed + SIZE_MAX. + + If the call is successfully verified as safe from buffer overflow + the function returns true, otherwise false.. */ + +static bool +check_sizes (int opt, tree exp, tree size, tree maxlen, tree str, tree objsize) +{ + /* The size of the largest object is half the address space, or + SSIZE_MAX. (This is way too permissive.) */ + tree maxobjsize = TYPE_MAX_VALUE (ssizetype); + + tree slen = NULL_TREE; + + /* Set to true when the exact number of bytes written by a string + function like strcpy is not known and the only thing that is + known is that it must be at least one (for the terminating nul). */ + bool at_least_one = false; + if (str) + { + /* STR is normally a pointer to string but as a special case + it can be an integer denoting the length of a string. */ + if (TREE_CODE (TREE_TYPE (str)) == POINTER_TYPE) + { + /* Try to determine the range of lengths the source string + refers to. If it can be determined add one to it for + the terminating nul. Otherwise, set it to one for + the same reason. */ + tree lenrange[2]; + get_range_strlen (str, lenrange); + if (lenrange[0]) + slen = fold_build2 (PLUS_EXPR, size_type_node, lenrange[0], + size_one_node); + else + { + at_least_one = true; + slen = size_one_node; + } + } + else + slen = str; + } + + if (!size && !maxlen) + { + /* When the only available piece of data is the object size + there is nothing to do. */ + if (!slen) + return true; + + /* Otherwise, when the length of the source sequence is known + (as with with strlen), set SIZE to it. */ + size = slen; + } + + if (!objsize) + objsize = maxobjsize; + + /* The SIZE is exact if it's non-null, constant, and in range of + unsigned HOST_WIDE_INT. */ + bool exactsize = size && tree_fits_uhwi_p (size); + + tree range[2] = { NULL_TREE, NULL_TREE }; + if (size) + get_size_range (size, range); + + /* First check the number of bytes to be written against the maximum + object size. */ + if (range[0] && tree_int_cst_lt (maxobjsize, range[0])) + { + location_t loc = tree_nonartificial_location (exp); + + if (range[0] == range[1]) + warning_at (loc, opt, + "%K%qD: specified size %wu " + "exceeds maximum object size %wu", + exp, get_callee_fndecl (exp), + tree_to_uhwi (range[0]), + tree_to_uhwi (maxobjsize)); + else + warning_at (loc, opt, + "%K%qD: specified size between %wu and %wu " + "exceeds maximum object size %wu", + exp, get_callee_fndecl (exp), + tree_to_uhwi (range[0]), + tree_to_uhwi (range[1]), + tree_to_uhwi (maxobjsize)); + return false; + } + + /* Next check the number of bytes to be written against the destination + object size. */ + if (range[0] || !exactsize || integer_all_onesp (size)) + { + if (range[0] + && ((tree_fits_uhwi_p (objsize) + && tree_int_cst_lt (objsize, range[0])) + || (tree_fits_uhwi_p (size) + && tree_int_cst_lt (size, range[0])))) + { + unsigned HOST_WIDE_INT uwir0 = tree_to_uhwi (range[0]); + + location_t loc = tree_nonartificial_location (exp); + + if (at_least_one) + warning_at (loc, opt, + "%K%qD: writing at least %wu byte into a region " + "of size %wu overflows the destination", + exp, get_callee_fndecl (exp), uwir0, + tree_to_uhwi (objsize)); + else if (range[0] == range[1]) + warning_at (loc, opt, + (uwir0 == 1 + ? G_("%K%qD: writing %wu byte into a region " + "of size %wu overflows the destination") + : G_("%K%qD writing %wu bytes into a region " + "of size %wu overflows the destination")), + exp, get_callee_fndecl (exp), uwir0, + tree_to_uhwi (objsize)); + else + warning_at (loc, opt, + "%K%qD: writing between %wu and %wu bytes " + "into a region of size %wu overflows " + "the destination", + exp, get_callee_fndecl (exp), uwir0, + tree_to_uhwi (range[1]), tree_to_uhwi (objsize)); + + /* Return error when an overflow has been detected. */ + return false; + } + } + + /* Check the maximum length of the source sequence against the size + of the destination object if known, or against the maximum size + of an object. */ + if (maxlen) + { + get_size_range (maxlen, range); + + if (range[0] && objsize && tree_fits_uhwi_p (objsize)) + { + location_t loc = tree_nonartificial_location (exp); + + if (tree_int_cst_lt (maxobjsize, range[0])) + { + /* Warn about crazy big sizes first since that's more + likely to be meaningful than saying that the bound + is greater than the object size if both are big. */ + if (range[0] == range[1]) + warning_at (loc, opt, + "%K%qD: specified bound %wu " + "exceeds maximum object size %wu", + exp, get_callee_fndecl (exp), + tree_to_uhwi (range[0]), + tree_to_uhwi (maxobjsize)); + else + warning_at (loc, opt, + "%K%qD: specified bound between %wu and %wu " + " exceeds maximum object size %wu", + exp, get_callee_fndecl (exp), + tree_to_uhwi (range[0]), + tree_to_uhwi (range[1]), + tree_to_uhwi (maxobjsize)); + + return false; + } + + if (objsize != maxobjsize && tree_int_cst_lt (objsize, range[0])) + { + if (range[0] == range[1]) + warning_at (loc, opt, + "%K%qD: specified bound %wu " + "exceeds the size %wu of the destination", + exp, get_callee_fndecl (exp), + tree_to_uhwi (range[0]), + tree_to_uhwi (objsize)); + else + warning_at (loc, opt, + "%K%qD: specified bound between %wu and %wu " + " exceeds the size %wu of the destination", + exp, get_callee_fndecl (exp), + tree_to_uhwi (range[0]), + tree_to_uhwi (range[1]), + tree_to_uhwi (objsize)); + return false; + } + } + } + + return true; +} + +/* Helper to compute the size of the object referenced by the DEST + expression which must of of pointer type, using Object Size type + OSTYPE (only the least significant 2 bits are used). Return + the size of the object if successful or NULL when the size cannot + be determined. */ + +static inline tree +compute_dest_size (tree dest, int ostype) +{ + unsigned HOST_WIDE_INT size; + if (compute_builtin_object_size (dest, ostype & 3, &size)) + return build_int_cst (sizetype, size); + + return NULL_TREE; +} + +/* Helper to determine and check the sizes of the source and the destination + of calls to __builtin_{bzero,memcpy,memset} calls. Use Object Size type-0 + regardless of the OPT_Wstringop_overflow_ setting. Returns true on success + (no overflow or invalid sizes), false otherwise. */ + +static bool +check_memop_sizes (tree exp, tree dest, tree size) +{ + if (!warn_stringop_overflow) + return true; + + /* For functions like memset and memcpy that operate on raw memory + try to determine the size of the largest destination object using + type-0 Object Size regardless of the object size type specified + by the option. */ + tree objsize = compute_dest_size (dest, 0); + + return check_sizes (OPT_Wstringop_overflow_, exp, + size, /*maxlen=*/NULL_TREE, /*str=*/NULL_TREE, objsize); +} + /* Expand a call EXP to the memcpy builtin. Return NULL_RTX if we failed, the caller should emit a normal call, otherwise try to get the result in TARGET, if convenient (and in @@ -3021,13 +3309,14 @@ expand_builtin_memcpy (tree exp, rtx target) if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) return NULL_RTX; - else - { - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - tree len = CALL_EXPR_ARG (exp, 2); - return expand_builtin_memcpy_args (dest, src, len, target, exp); - } + + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + tree len = CALL_EXPR_ARG (exp, 2); + + check_memop_sizes (exp, dest, len); + + return expand_builtin_memcpy_args (dest, src, len, target, exp); } /* Expand an instrumented call EXP to the memcpy builtin. @@ -3075,15 +3364,20 @@ expand_builtin_mempcpy (tree exp, rtx target, machine_mode mode) if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) return NULL_RTX; - else - { - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - tree len = CALL_EXPR_ARG (exp, 2); - return expand_builtin_mempcpy_args (dest, src, len, - target, mode, /*endp=*/ 1, - exp); - } + + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + tree len = CALL_EXPR_ARG (exp, 2); + + /* Avoid expanding mempcpy into memcpy when the call is determined + to overflow the buffer. This also prevents the same overflow + from being diagnosed again when expanding memcpy. */ + if (!check_memop_sizes (exp, dest, len)) + return NULL_RTX; + + return expand_builtin_mempcpy_args (dest, src, len, + target, mode, /*endp=*/ 1, + exp); } /* Expand an instrumented call EXP to the mempcpy builtin. @@ -3255,6 +3549,33 @@ expand_movstr (tree dest, tree src, rtx target, int endp) return target; } +/* Do some very basic size validation of a call to the strcpy builtin + given by EXP. Return NULL_RTX to have the built-in expand to a call + to the library function. */ + +static rtx +expand_builtin_strcat (tree exp, rtx) +{ + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE) + || !warn_stringop_overflow) + return NULL_RTX; + + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + + /* There is no way here to determine the length of the string in + the destination to which the SRC string is being appended so + just diagnose cases when the souce string is longer than + the destination object. */ + + tree destsize = compute_dest_size (dest, warn_stringop_overflow - 1); + + check_sizes (OPT_Wstringop_overflow_, + exp, /*size=*/NULL_TREE, /*maxlen=*/NULL_TREE, src, destsize); + + return NULL_RTX; +} + /* Expand expression EXP, which is a call to the strcpy builtin. Return NULL_RTX if we failed the caller should emit a normal call, otherwise try to get the result in TARGET, if convenient (and in mode MODE if that's @@ -3263,13 +3584,20 @@ expand_movstr (tree dest, tree src, rtx target, int endp) static rtx expand_builtin_strcpy (tree exp, rtx target) { - if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) - { - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - return expand_builtin_strcpy_args (dest, src, target); - } - return NULL_RTX; + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + return NULL_RTX; + + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + + if (warn_stringop_overflow) + { + tree destsize = compute_dest_size (dest, warn_stringop_overflow - 1); + check_sizes (OPT_Wstringop_overflow_, + exp, /*size=*/NULL_TREE, /*maxlen=*/NULL_TREE, src, destsize); + } + + return expand_builtin_strcpy_args (dest, src, target); } /* Helper function to do the actual work for expand_builtin_strcpy. The @@ -3377,6 +3705,131 @@ builtin_strncpy_read_str (void *data, HOST_WIDE_INT offset, return c_readstr (str + offset, mode); } +/* Helper to check the sizes of sequences and the destination of calls + to __builtin_strncat and __builtin___strncat_chk. Returns true on + success (no overflow or invalid sizes), false otherwise. */ + +static bool +check_strncat_sizes (tree exp, tree objsize) +{ + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + tree maxlen = CALL_EXPR_ARG (exp, 2); + + /* Try to determine the range of lengths that the source expression + refers to. */ + tree lenrange[2]; + get_range_strlen (src, lenrange); + + /* Try to verify that the destination is big enough for the shortest + string. */ + + if (!objsize && warn_stringop_overflow) + { + /* If it hasn't been provided by __strncat_chk, try to determine + the size of the destination object into which the source is + being copied. */ + objsize = compute_dest_size (dest, warn_stringop_overflow - 1); + } + + /* Add one for the terminating nul. */ + tree srclen = (lenrange[0] + ? fold_build2 (PLUS_EXPR, size_type_node, lenrange[0], + size_one_node) + : NULL_TREE); + + /* Strncat copies at most MAXLEN bytes and always appends the terminating + nul so the specified upper bound should never be equal to (or greater + than) the size of the destination. */ + if (tree_fits_uhwi_p (maxlen) && tree_fits_uhwi_p (objsize) + && tree_int_cst_equal (objsize, maxlen)) + { + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_overflow_, + "specified bound %wu " + "equals the size of the destination", + tree_to_uhwi (maxlen)); + + return false; + } + + if (!srclen + || (maxlen && tree_fits_uhwi_p (maxlen) + && tree_fits_uhwi_p (srclen) + && tree_int_cst_lt (maxlen, srclen))) + srclen = maxlen; + + /* The number of bytes to write is LEN but check_sizes will also + check SRCLEN if LEN's value isn't known. */ + return check_sizes (OPT_Wstringop_overflow_, + exp, /*size=*/NULL_TREE, maxlen, srclen, objsize); +} + +/* Similar to expand_builtin_strcat, do some very basic size validation + of a call to the strcpy builtin given by EXP. Return NULL_RTX to have + the built-in expand to a call to the library function. */ + +static rtx +expand_builtin_strncat (tree exp, rtx) +{ + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE) + || !warn_stringop_overflow) + return NULL_RTX; + + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + /* The upper bound on the number of bytes to write. */ + tree maxlen = CALL_EXPR_ARG (exp, 2); + /* The length of the source sequence. */ + tree slen = c_strlen (src, 1); + + /* Try to determine the range of lengths that the source expression + refers to. */ + tree lenrange[2]; + if (slen) + lenrange[0] = lenrange[1] = slen; + else + get_range_strlen (src, lenrange); + + /* Try to verify that the destination is big enough for the shortest + string. First try to determine the size of the destination object + into which the source is being copied. */ + tree destsize = compute_dest_size (dest, warn_stringop_overflow - 1); + + /* Add one for the terminating nul. */ + tree srclen = (lenrange[0] + ? fold_build2 (PLUS_EXPR, size_type_node, lenrange[0], + size_one_node) + : NULL_TREE); + + /* Strncat copies at most MAXLEN bytes and always appends the terminating + nul so the specified upper bound should never be equal to (or greater + than) the size of the destination. */ + if (tree_fits_uhwi_p (maxlen) && tree_fits_uhwi_p (destsize) + && tree_int_cst_equal (destsize, maxlen)) + { + warning_at (EXPR_LOCATION (exp), OPT_Wstringop_overflow_, + "specified bound %wu " + "equals the size of the destination", + tree_to_uhwi (maxlen)); + + return NULL_RTX; + } + + if (!srclen + || (maxlen && tree_fits_uhwi_p (maxlen) + && tree_fits_uhwi_p (srclen) + && tree_int_cst_lt (maxlen, srclen))) + srclen = maxlen; + + /* The number of bytes to write is LEN but check_sizes will also + check SRCLEN if LEN's value isn't known. */ + check_sizes (OPT_Wstringop_overflow_, + exp, /*size=*/NULL_TREE, maxlen, srclen, destsize); + + return NULL_RTX; +} + /* Expand expression EXP, which is a call to the strncpy builtin. Return NULL_RTX if we failed the caller should emit a normal call. */ @@ -3390,9 +3843,33 @@ expand_builtin_strncpy (tree exp, rtx target) { tree dest = CALL_EXPR_ARG (exp, 0); tree src = CALL_EXPR_ARG (exp, 1); + /* The number of bytes to write (not the maximum). */ tree len = CALL_EXPR_ARG (exp, 2); + /* The length of the source sequence. */ tree slen = c_strlen (src, 1); + if (warn_stringop_overflow) + { + /* Try to determine the range of lengths that the source expression + refers to. */ + tree lenrange[2]; + if (slen) + lenrange[0] = lenrange[1] = slen; + else + { + get_range_strlen (src, lenrange); + slen = lenrange[0]; + } + + tree destsize = compute_dest_size (dest, + warn_stringop_overflow - 1); + + /* The number of bytes to write is LEN but check_sizes will also + check SLEN if LEN's value isn't known. */ + check_sizes (OPT_Wstringop_overflow_, + exp, len, /*maxlen=*/NULL_TREE, slen, destsize); + } + /* We must be passed a constant len and src parameter. */ if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen)) return NULL_RTX; @@ -3480,13 +3957,14 @@ expand_builtin_memset (tree exp, rtx target, machine_mode mode) if (!validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE)) return NULL_RTX; - else - { - tree dest = CALL_EXPR_ARG (exp, 0); - tree val = CALL_EXPR_ARG (exp, 1); - tree len = CALL_EXPR_ARG (exp, 2); - return expand_builtin_memset_args (dest, val, len, target, mode, exp); - } + + tree dest = CALL_EXPR_ARG (exp, 0); + tree val = CALL_EXPR_ARG (exp, 1); + tree len = CALL_EXPR_ARG (exp, 2); + + check_memop_sizes (exp, dest, len); + + return expand_builtin_memset_args (dest, val, len, target, mode, exp); } /* Expand expression EXP, which is an instrumented call to the memset builtin. @@ -3667,20 +4145,21 @@ expand_builtin_memset_args (tree dest, tree val, tree len, static rtx expand_builtin_bzero (tree exp) { - tree dest, size; - location_t loc = EXPR_LOCATION (exp); - if (!validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) return NULL_RTX; - dest = CALL_EXPR_ARG (exp, 0); - size = CALL_EXPR_ARG (exp, 1); + tree dest = CALL_EXPR_ARG (exp, 0); + tree size = CALL_EXPR_ARG (exp, 1); + + check_memop_sizes (exp, dest, size); /* New argument list transforming bzero(ptr x, int y) to memset(ptr x, int 0, size_t y). This is done this way so that if it isn't expanded inline, we fallback to calling bzero instead of memset. */ + location_t loc = EXPR_LOCATION (exp); + return expand_builtin_memset_args (dest, integer_zero_node, fold_convert_loc (loc, size_type_node, size), @@ -6205,12 +6684,24 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode, return target; break; + case BUILT_IN_STRCAT: + target = expand_builtin_strcat (exp, target); + if (target) + return target; + break; + case BUILT_IN_STRCPY: target = expand_builtin_strcpy (exp, target); if (target) return target; break; + case BUILT_IN_STRNCAT: + target = expand_builtin_strncat (exp, target); + if (target) + return target; + break; + case BUILT_IN_STRNCPY: target = expand_builtin_strncpy (exp, target); if (target) @@ -9052,22 +9543,22 @@ expand_builtin_memory_chk (tree exp, rtx target, machine_mode mode, len = CALL_EXPR_ARG (exp, 2); size = CALL_EXPR_ARG (exp, 3); - if (! tree_fits_uhwi_p (size)) + bool sizes_ok = check_sizes (OPT_Wstringop_overflow_, + exp, len, /*maxlen=*/NULL_TREE, + /*str=*/NULL_TREE, size); + + if (!tree_fits_uhwi_p (size)) return NULL_RTX; if (tree_fits_uhwi_p (len) || integer_all_onesp (size)) { - tree fn; - - if (! integer_all_onesp (size) && tree_int_cst_lt (size, len)) - { - warning_at (tree_nonartificial_location (exp), - 0, "%Kcall to %D will always overflow destination buffer", - exp, get_callee_fndecl (exp)); - return NULL_RTX; - } + /* Avoid transforming the checking call to an ordinary one when + an overflow has been detected or when the call couldn't be + validated because the size is not constant. */ + if (!sizes_ok && !integer_all_onesp (size) && tree_int_cst_lt (size, len)) + return NULL_RTX; - fn = NULL_TREE; + tree fn = NULL_TREE; /* If __builtin_mem{cpy,pcpy,move,set}_chk is used, assume mem{cpy,pcpy,move,set} is available. */ switch (fcode) @@ -9153,68 +9644,68 @@ expand_builtin_memory_chk (tree exp, rtx target, machine_mode mode, static void maybe_emit_chk_warning (tree exp, enum built_in_function fcode) { - int is_strlen = 0; - tree len, size; - location_t loc = tree_nonartificial_location (exp); + /* The source string. */ + tree srcstr = NULL_TREE; + /* The size of the destination object. */ + tree objsize = NULL_TREE; + /* The string that is being concatenated with (as in __strcat_chk) + or null if it isn't. */ + tree catstr = NULL_TREE; + /* The maximum length of the source sequence in a bounded operation + (such as __strncat_chk) or null if the operation isn't bounded + (such as __strcat_chk). */ + tree maxlen = NULL_TREE; switch (fcode) { case BUILT_IN_STRCPY_CHK: case BUILT_IN_STPCPY_CHK: - /* For __strcat_chk the warning will be emitted only if overflowing - by at least strlen (dest) + 1 bytes. */ + srcstr = CALL_EXPR_ARG (exp, 1); + objsize = CALL_EXPR_ARG (exp, 2); + break; + case BUILT_IN_STRCAT_CHK: - len = CALL_EXPR_ARG (exp, 1); - size = CALL_EXPR_ARG (exp, 2); - is_strlen = 1; + /* For __strcat_chk the warning will be emitted only if overflowing + by at least strlen (dest) + 1 bytes. */ + catstr = CALL_EXPR_ARG (exp, 0); + srcstr = CALL_EXPR_ARG (exp, 1); + objsize = CALL_EXPR_ARG (exp, 2); break; + case BUILT_IN_STRNCAT_CHK: + catstr = CALL_EXPR_ARG (exp, 0); + srcstr = CALL_EXPR_ARG (exp, 1); + maxlen = CALL_EXPR_ARG (exp, 2); + objsize = CALL_EXPR_ARG (exp, 3); + break; + case BUILT_IN_STRNCPY_CHK: case BUILT_IN_STPNCPY_CHK: - len = CALL_EXPR_ARG (exp, 2); - size = CALL_EXPR_ARG (exp, 3); + srcstr = CALL_EXPR_ARG (exp, 1); + maxlen = CALL_EXPR_ARG (exp, 2); + objsize = CALL_EXPR_ARG (exp, 3); break; + case BUILT_IN_SNPRINTF_CHK: case BUILT_IN_VSNPRINTF_CHK: - len = CALL_EXPR_ARG (exp, 1); - size = CALL_EXPR_ARG (exp, 3); + maxlen = CALL_EXPR_ARG (exp, 1); + objsize = CALL_EXPR_ARG (exp, 3); break; default: gcc_unreachable (); } - if (!len || !size) - return; - - if (! tree_fits_uhwi_p (size) || integer_all_onesp (size)) - return; - - if (is_strlen) + if (catstr && maxlen) { - len = c_strlen (len, 1); - if (! len || ! tree_fits_uhwi_p (len) || tree_int_cst_lt (len, size)) + /* Check __strncat_chk. There is no way to determine the length + of the string to which the source string is being appended so + just warn when the length of the source string is not known. */ + if (!check_strncat_sizes (exp, objsize)) return; } - else if (fcode == BUILT_IN_STRNCAT_CHK) - { - tree src = CALL_EXPR_ARG (exp, 1); - if (! src || ! tree_fits_uhwi_p (len) || tree_int_cst_lt (len, size)) - return; - src = c_strlen (src, 1); - if (! src || ! tree_fits_uhwi_p (src)) - { - warning_at (loc, 0, "%Kcall to %D might overflow destination buffer", - exp, get_callee_fndecl (exp)); - return; - } - else if (tree_int_cst_lt (src, size)) - return; - } - else if (! tree_fits_uhwi_p (len) || ! tree_int_cst_lt (size, len)) - return; - warning_at (loc, 0, "%Kcall to %D will always overflow destination buffer", - exp, get_callee_fndecl (exp)); + check_sizes (OPT_Wstringop_overflow_, exp, + /*size=*/NULL_TREE, maxlen, srcstr, objsize); } /* Emit warning if a buffer overflow is detected at compile time @@ -9268,10 +9759,10 @@ maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode) else return; - if (! tree_int_cst_lt (len, size)) - warning_at (tree_nonartificial_location (exp), - 0, "%Kcall to %D will always overflow destination buffer", - exp, get_callee_fndecl (exp)); + /* Add one for the terminating nul. */ + len = fold_build2 (PLUS_EXPR, TREE_TYPE (len), len, size_one_node); + check_sizes (OPT_Wstringop_overflow_, + exp, /*size=*/NULL_TREE, /*maxlen=*/NULL_TREE, len, size); } /* Emit warning if a free is called with address of a variable. */ diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog index 58907981b96..aba0b14fa26 100644 --- a/gcc/c-family/ChangeLog +++ b/gcc/c-family/ChangeLog @@ -1,3 +1,11 @@ +2016-12-07 Martin Sebor + + PR c/53562 + PR middle-end/77784 + PR middle-end/78149 + PR middle-end/78138 + * c.opt (-Wstringop-overflow): New option. + 2016-12-02 Maxim Ostapenko * c-attribs.c (asan odr indicator): New attribute. diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 2d47d54563a..288e4ce12fb 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -684,6 +684,16 @@ Wsizeof-array-argument C ObjC C++ ObjC++ Var(warn_sizeof_array_argument) Warning Init(1) Warn when sizeof is applied on a parameter declared as an array. +Wstringop-overflow +C ObjC C++ ObjC++ Warning Alias(Wstringop-overflow=, 2, 0) +Warn about buffer overflow in string manipulation functions like memcpy +and strcpy. + +Wstringop-overflow= +C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Init(2) Warning +Under the control of Object Size type, warn about buffer overflow in string +manipulation functions like memcpy and strcpy. + Wsuggest-attribute=format C ObjC C++ ObjC++ Var(warn_suggest_attribute_format) Warning Warn about functions which might be candidates for format attributes. diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 034ae983468..5622c0f107e 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -304,6 +304,7 @@ Objective-C and Objective-C++ Dialects}. -Wsizeof-pointer-memaccess -Wsizeof-array-argument @gol -Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol -Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol +-Wstringop-overflow=@var{n} @gol -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @gol -Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol -Wmissing-format-attribute -Wsubobject-linkage @gol @@ -4936,6 +4937,86 @@ comparisons, so this warning level gives a very large number of false positives. @end table +@item -Wstringop-overflow +@itemx -Wstringop-overflow=@var{type} +@opindex Wstringop-overflow +@opindex Wno-stringop-overflow +Warn for calls to string manipulation functions such as @code{memcpy} and +@code{strcpy} that are determined to overflow the destination buffer. The +optional argument is one greater than the type of Object Size Checking to +perform to determine the size of the destination. @xref{Object Size Checking}. +The argument is meaningful only for functions that operate on character arrays +but not for raw memory functions like @code{memcpy} which always make use +of Object Size type-0. The option also warns for calls that specify a size +in excess of the largest possible object or at most @code{SIZE_MAX / 2} bytes. +The option produces the best results with optimization enabled but can detect +a small subset of simple buffer overflows even without optimization in +calls to the GCC built-in functions like @code{__builtin_memcpy} that +correspond to the standard functions. In any case, the option warns about +just a subset of buffer overflows detected by the corresponding overflow +checking built-ins. For example, the option will issue a warning for +the @code{strcpy} call below because it copies at least 5 characters +(the string @code{"blue"} including the terminating NUL) into the buffer +of size 4. + +@smallexample +enum Color @{ blue, purple, yellow @}; +const char* f (enum Color clr) +@{ + static char buf [4]; + const char *str; + switch (clr) + @{ + case blue: str = "blue"; break; + case purple: str = "purple"; break; + case yellow: str = "yellow"; break; + @} + + return strcpy (buf, str); // warning here +@} +@end smallexample + +Option @option{-Wstringop-overflow=2} is enabled by default. + +@table @gcctabopt +@item -Wstringop-overflow +@item -Wstringop-overflow=1 +@opindex Wstringop-overflow +@opindex Wno-stringop-overflow +The @option{-Wstringop-overflow=1} option uses type-zero Object Size Checking +to determine the sizes of destination objects. This is the default setting +of the option. At this setting the option will not warn for writes past +the end of subobjects of larger objects accessed by pointers unless the +size of the largest surrounding object is known. When the destination may +be one of several objects it is assumed to be the largest one of them. On +Linux systems, when optimization is enabled at this setting the option warns +for the same code as when the @code{_FORTIFY_SOURCE} macro is defined to +a non-zero value. + +@item -Wstringop-overflow=2 +The @option{-Wstringop-overflow=2} option uses type-one Object Size Checking +to determine the sizes of destination objects. At this setting the option +will warn about overflows when writing to members of the largest complete +objects whose exact size is known. It will, however, not warn for excessive +writes to the same members of unknown objects referenced by pointers since +they may point to arrays containing unknown numbers of elements. + +@item -Wstringop-overflow=3 +The @option{-Wstringop-overflow=3} option uses type-two Object Size Checking +to determine the sizes of destination objects. At this setting the option +warns about overflowing the smallest object or data member. This is the +most restrictive setting of the option that may result in warnings for safe +code. + +@item -Wstringop-overflow=4 +The @option{-Wstringop-overflow=4} option uses type-three Object Size Checking +to determine the sizes of destination objects. At this setting the option +will warn about overflowing any data members, and when the destination is +one of several objects it uses the size of the largest of them to decide +whether to issue a warning. Similarly to @option{-Wstringop-overflow=3} this +setting of the option may result in warnings for benign code. +@end table + @item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @opindex Wsuggest-attribute= @opindex Wno-suggest-attribute= diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c index e86c4dc71e1..8de9a1e31d2 100644 --- a/gcc/gimple-ssa-sprintf.c +++ b/gcc/gimple-ssa-sprintf.c @@ -774,7 +774,23 @@ get_width_and_precision (const conversion_spec &spec, if (spec.star_width) { if (TREE_CODE (spec.star_width) == INTEGER_CST) - width = abs (tree_to_shwi (spec.star_width)); + { + width = tree_to_shwi (spec.star_width); + if (width < 0) + { + if (width == HOST_WIDE_INT_MIN) + { + /* Avoid undefined behavior due to negating a minimum. + This case will be diagnosed since it will result in + more than INT_MAX bytes on output, either by the + directive itself (when INT_MAX < HOST_WIDE_INT_MAX) + or by the format function itself. */ + width = HOST_WIDE_INT_MAX; + } + else + width = -width; + } + } else width = HOST_WIDE_INT_MIN; } @@ -1261,9 +1277,9 @@ format_floating (const conversion_spec &spec, int width, int prec) res.range.min = 2 + (prec < 0 ? 6 : prec); /* Compute the maximum just once. */ - static const int f_max[] = { - format_floating_max (double_type_node, 'f'), - format_floating_max (long_double_type_node, 'f') + const int f_max[] = { + format_floating_max (double_type_node, 'f', prec), + format_floating_max (long_double_type_node, 'f', prec) }; res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : f_max [ldbl]; @@ -1279,9 +1295,9 @@ format_floating (const conversion_spec &spec, int width, int prec) res.range.min = 2 + (prec < 0 ? 6 : prec); /* Compute the maximum just once. */ - static const int g_max[] = { - format_floating_max (double_type_node, 'g'), - format_floating_max (long_double_type_node, 'g') + const int g_max[] = { + format_floating_max (double_type_node, 'g', prec), + format_floating_max (long_double_type_node, 'g', prec) }; res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : g_max [ldbl]; @@ -2743,19 +2759,27 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi) { dstsize = tree_to_uhwi (size); /* No object can be larger than SIZE_MAX bytes (half the address - space) on the target. This imposes a limit that's one byte - less than that. + space) on the target. The functions are defined only for output of at most INT_MAX bytes. Specifying a bound in excess of that limit effectively defeats the bounds checking (and on some implementations such as Solaris cause the function to fail with EINVAL). */ - if (dstsize >= target_size_max () / 2) - warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, - "specified destination size %wu is too large", - dstsize); + if (dstsize > target_size_max () / 2) + { + /* Avoid warning if -Wstringop-overflow is specified since + it also warns for the same thing though only for the + checking built-ins. */ + if ((idx_objsize == HOST_WIDE_INT_M1U + || !warn_stringop_overflow)) + warning_at (gimple_location (info.callstmt), + OPT_Wformat_length_, + "specified bound %wu exceeds maximum object size " + "%wu", + dstsize, target_size_max () / 2); + } else if (dstsize > target_int_max ()) warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, - "specified destination size %wu exceeds %", + "specified bound %wu exceeds %", dstsize); } else if (TREE_CODE (size) == SSA_NAME) @@ -2800,10 +2824,15 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi) info.objsize = dstsize < objsize ? dstsize : objsize; if (info.bounded - && dstsize < target_size_max () / 2 && objsize < dstsize) + && dstsize < target_size_max () / 2 && objsize < dstsize + /* Avoid warning if -Wstringop-overflow is specified since + it also warns for the same thing though only for the + checking built-ins. */ + && (idx_objsize == HOST_WIDE_INT_M1U + || !warn_stringop_overflow)) { warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, - "specified size %wu exceeds the size %wu " + "specified bound %wu exceeds the size %wu " "of the destination object", dstsize, objsize); } } diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 33f1f86a055..27225c24837 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,32 @@ +2016-12-07 Martin Sebor + + PR middle-end/77784 + PR middle-end/78149 + PR middle-end/78138 + + * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust expected diagnostic. + * g++.dg/ext/builtin-object-size3.C (bar): Same. + * g++.dg/ext/strncpy-chk1.C: Same. + * g++.dg/opt/memcpy1.C: Same. + * g++.dg/torture/Wsizeof-pointer-memaccess1.C: Same. + * gcc.c-torture/compile/pr55569.c: Disable -Wstringop-overflow. + * gcc.dg/Wobjsize-1.c: Adjust expected diagnostic. + * gcc.dg/attr-alloc_size.c: Same. + * gcc.dg/builtin-stringop-chk-1.c: Adjust expected diagnostic. + * gcc.dg/builtin-stringop-chk-2.c: Same. + * gcc.dg/builtin-stringop-chk-4.c: New test. + * gcc.dg/builtin-strncat-chk-1.c: Adjust expected diagnostic. + * gcc.dg/memcpy-2.c: Same. + * gcc.dg/pr40340-1.c: Same. + * gcc.dg/pr40340-2.c (main): Same. + * gcc.dg/pr40340-5.c (main): Same. + * gcc.dg/torture/Wsizeof-pointer-memaccess1.c: Same. + * gcc.dg/torture/pr71132.c: Disable -Wstringop-overflow. + * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Adjust text of expected + warning. + * gfortran.dg/char_length_3.f90: Prune expected warnings. + * gfortran.dg/pr38868.f: Add expected warnings. + 2016-12-07 Michael Meissner PR target/72717 diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c index d9ec7e2cace..9a023739709 100644 --- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c +++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c @@ -481,4 +481,4 @@ f4 (char *x, char **y, int z, char w[64]) stpncpy (x, s3, sizeof (s3)); } -/* { dg-prune-output "\[\n\r\]*will always overflow\[\n\r\]*" } */ +/* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */ diff --git a/gcc/testsuite/g++.dg/ext/builtin-object-size3.C b/gcc/testsuite/g++.dg/ext/builtin-object-size3.C index 09263e58719..0207f9ab667 100644 --- a/gcc/testsuite/g++.dg/ext/builtin-object-size3.C +++ b/gcc/testsuite/g++.dg/ext/builtin-object-size3.C @@ -20,7 +20,7 @@ bar () { int *p = new int; int *q = new int[4]; - MEMCPY (p, "abcdefghijklmnopqrstuvwxyz", sizeof (int) + 1); // { dg-warning "will always overflow destination buffer" } - MEMCPY (q, "abcdefghijklmnopqrstuvwxyz", 4 * sizeof (int) + 1); // { dg-warning "will always overflow destination buffer" } + MEMCPY (p, "abcdefghijklmnopqrstuvwxyz", sizeof (int) + 1); // { dg-warning "writing" } + MEMCPY (q, "abcdefghijklmnopqrstuvwxyz", 4 * sizeof (int) + 1); // { dg-warning "writing" } baz (p, q); } diff --git a/gcc/testsuite/g++.dg/ext/strncpy-chk1.C b/gcc/testsuite/g++.dg/ext/strncpy-chk1.C index ebafc99887b..d67d6bfea45 100644 --- a/gcc/testsuite/g++.dg/ext/strncpy-chk1.C +++ b/gcc/testsuite/g++.dg/ext/strncpy-chk1.C @@ -9,7 +9,7 @@ struct B { char z[50]; }; inline void foo (char *dest, const char *__restrict src, __SIZE_TYPE__ n) { - __builtin___strncpy_chk (dest, src, n, __builtin_object_size (dest, 0)); // { dg-warning "will always overflow" } + __builtin___strncpy_chk (dest, src, n, __builtin_object_size (dest, 0)); // { dg-warning "specified bound 36 exceeds the size 35 of the destination" } } void bar (const char *, int); diff --git a/gcc/testsuite/g++.dg/opt/memcpy1.C b/gcc/testsuite/g++.dg/opt/memcpy1.C index f2913459973..e2b1dd2cdf0 100644 --- a/gcc/testsuite/g++.dg/opt/memcpy1.C +++ b/gcc/testsuite/g++.dg/opt/memcpy1.C @@ -59,7 +59,10 @@ namespace CS } uint8 Clip () { - __builtin_memcpy (this->OutP, InP, OutV * sizeof (csVector2)); + // OutV is initialized to SIZE_MAX in the ctor above causing + // the multiplication below to produce a very large number + // in excess of the maximum possible object size (SIZE_MAX/2). + __builtin_memcpy (this->OutP, InP, OutV * sizeof (csVector2)); // { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } } }; } diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C index 8b5c33e24b3..2e6189b3147 100644 --- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C +++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C @@ -713,4 +713,4 @@ f4 (char *x, char **y, int z, char w[64]) return z; } -// { dg-prune-output "\[\n\r\]*will always overflow\[\n\r\]*" } +// { dg-prune-output "\[\n\r\]*overflows\[\n\r\]*" } diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C index 0e99568d3f3..a216f470333 100644 --- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C +++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess2.C @@ -1,7 +1,8 @@ // Test -Wsizeof-pointer-memaccess warnings. // { dg-do compile } -// { dg-options "-Wall -Wno-sizeof-array-argument" } -// Test just twice, once with -O0 non-fortified, once with -O2 fortified. +// { dg-options "-Wall -Wno-sizeof-array-argument -Wno-stringop-overflow" } +// Test just twice, once with -O0 non-fortified, once with -O2 fortified, +// suppressing buffer overflow warnings. // { dg-skip-if "" { *-*-* } { "*" } { "-O0" "-O2" } } // { dg-skip-if "" { *-*-* } { "-flto" } { "" } } diff --git a/gcc/testsuite/gcc.c-torture/compile/pr55569.c b/gcc/testsuite/gcc.c-torture/compile/pr55569.c index cffbcfc7521..cf274cdbb99 100644 --- a/gcc/testsuite/gcc.c-torture/compile/pr55569.c +++ b/gcc/testsuite/gcc.c-torture/compile/pr55569.c @@ -1,4 +1,4 @@ -/* { dg-options "-ftree-vectorize" } */ +/* { dg-options "-Wno-stringop-overflow -ftree-vectorize" } */ int *bar (void); void @@ -6,6 +6,10 @@ foo (void) { long x; int *y = bar (); - for (x = -1 / sizeof (int); x; --x, ++y) - *y = 0; + + /* The loop below may be optimized to a call to memset with a size + that's in excess of the maximum object size. This is diagnosed + by the -Wstringop-overflow option. */ + for (x = -1 / sizeof (int); x; --x, ++y) + *y = 0; } diff --git a/gcc/testsuite/gcc.dg/Wobjsize-1.c b/gcc/testsuite/gcc.dg/Wobjsize-1.c index 291cfb9be91..211e0680a1d 100644 --- a/gcc/testsuite/gcc.dg/Wobjsize-1.c +++ b/gcc/testsuite/gcc.dg/Wobjsize-1.c @@ -10,6 +10,6 @@ int main(int argc, char **argv) return 0; } -/* { dg-warning "will always overflow destination buffer" "" { target *-*-* } 6 } */ +/* { dg-warning "writing" "" { target *-*-* } 6 } */ /* { dg-message "file included" "included" { target *-*-* } 0 } */ /* { dg-message "inlined from" "inlined" { target *-*-* } 0 } */ diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size.c b/gcc/testsuite/gcc.dg/attr-alloc_size.c index e8129ceae96..f50ba7c53db 100644 --- a/gcc/testsuite/gcc.dg/attr-alloc_size.c +++ b/gcc/testsuite/gcc.dg/attr-alloc_size.c @@ -22,15 +22,15 @@ test (void) strcpy (p, "Hello"); p = malloc1 (6); strcpy (p, "Hello"); - strcpy (p, "Hello World"); /* { dg-warning "will always overflow" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ p = malloc2 (__INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 6); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "will always overflow" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ p = calloc1 (2, 5); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "will always overflow" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ p = calloc2 (2, __INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 5); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "will always overflow" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ } diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c index e491ff52680..7689287cb1e 100644 --- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c @@ -8,7 +8,10 @@ extern void abort (void); #include "../gcc.c-torture/execute/builtins/chk.h" -#include + +#define va_list __builtin_va_list +#define va_start __builtin_va_start +#define va_end __builtin_va_end volatile void *vx; char buf1[20]; @@ -22,60 +25,61 @@ test (int arg, ...) char *p = &buf1[10], *q; memcpy (&buf2[19], "ab", 1); - memcpy (&buf2[19], "ab", 2); /* { dg-warning "will always overflow" "memcpy" } */ + memcpy (&buf2[19], "ab", 2); /* { dg-warning "writing 2 bytes into a region of size 1" "memcpy" } */ vx = mempcpy (&buf2[19], "ab", 1); - vx = mempcpy (&buf2[19], "ab", 2); /* { dg-warning "will always overflow" "mempcpy" } */ + vx = mempcpy (&buf2[19], "ab", 2); /* { dg-warning "writing 2 " "mempcpy" } */ memmove (&buf2[18], &buf1[10], 2); - memmove (&buf2[18], &buf1[10], 3); /* { dg-warning "will always overflow" "memmove" } */ + memmove (&buf2[18], &buf1[10], 3); /* { dg-warning "writing 3 " "memmove" } */ memset (&buf2[16], 'a', 4); - memset (&buf2[15], 'b', 6); /* { dg-warning "will always overflow" "memset" } */ + memset (&buf2[15], 'b', 6); /* { dg-warning "writing 6 " "memset" } */ strcpy (&buf2[18], "a"); - strcpy (&buf2[18], "ab"); /* { dg-warning "will always overflow" "strcpy" } */ + strcpy (&buf2[18], "ab"); /* { dg-warning "writing 3 " "strcpy" } */ vx = stpcpy (&buf2[18], "a"); - vx = stpcpy (&buf2[18], "ab"); /* { dg-warning "will always overflow" "stpcpy" } */ + vx = stpcpy (&buf2[18], "ab"); /* { dg-warning "writing 3" "stpcpy" } */ strncpy (&buf2[18], "a", 2); - strncpy (&buf2[18], "a", 3); /* { dg-warning "will always overflow" "strncpy" } */ + strncpy (&buf2[18], "a", 3); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" "strncpy" } */ strncpy (&buf2[18], "abc", 2); - strncpy (&buf2[18], "abc", 3); /* { dg-warning "will always overflow" "strncpy" } */ + strncpy (&buf2[18], "abc", 3); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" "strncpy" } */ memset (buf2, '\0', sizeof (buf2)); strcat (&buf2[18], "a"); memset (buf2, '\0', sizeof (buf2)); - strcat (&buf2[18], "ab"); /* { dg-warning "will always overflow" "strcat" } */ + strcat (&buf2[18], "ab"); /* { dg-warning "writing 3 " "strcat" } */ sprintf (&buf2[18], "%s", buf1); sprintf (&buf2[18], "%s", "a"); - sprintf (&buf2[18], "%s", "ab"); /* { dg-warning "will always overflow" "sprintf" } */ + sprintf (&buf2[18], "%s", "ab"); /* { dg-warning "writing 3 " "sprintf" } */ sprintf (&buf2[18], "a"); - sprintf (&buf2[18], "ab"); /* { dg-warning "will always overflow" "sprintf" } */ + sprintf (&buf2[18], "ab"); /* { dg-warning "writing 3 " "sprintf" } */ snprintf (&buf2[18], 2, "%d", x); /* N argument to snprintf is the size of the buffer. Although this particular call wouldn't overflow buf2, incorrect buffer size was passed to it and therefore we want a warning and runtime failure. */ - snprintf (&buf2[18], 3, "%d", x); /* { dg-warning "will always overflow" "snprintf" } */ + snprintf (&buf2[18], 3, "%d", x); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" "snprintf" } */ va_start (ap, arg); vsprintf (&buf2[18], "a", ap); va_end (ap); + va_start (ap, arg); - vsprintf (&buf2[18], "ab", ap); /* { dg-warning "will always overflow" "vsprintf" } */ + vsprintf (&buf2[18], "ab", ap); /* { dg-warning "writing 3" "vsprintf" } */ va_end (ap); va_start (ap, arg); vsnprintf (&buf2[18], 2, "%s", ap); va_end (ap); va_start (ap, arg); /* See snprintf above. */ - vsnprintf (&buf2[18], 3, "%s", ap); /* { dg-warning "will always overflow" "vsnprintf" } */ + vsnprintf (&buf2[18], 3, "%s", ap); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" "vsnprintf" } */ va_end (ap); p = p + 10; memset (p, 'd', 0); - q = strcpy (p, ""); /* { dg-warning "will always overflow" "strcpy" } */ + q = strcpy (p, ""); /* { dg-warning "writing 1 " "strcpy" } */ /* This invokes undefined behavior, since we are past the end of buf1. */ p = p + 10; - memset (p, 'd', 1); /* { dg-warning "will always overflow" "memset" } */ + memset (p, 'd', 1); /* { dg-warning "writing 1 " "memset" } */ memset (q, 'd', 0); - memset (q, 'd', 1); /* { dg-warning "will always overflow" "memset" } */ + memset (q, 'd', 1); /* { dg-warning "writing 1 " "memset" } */ q = q - 10; memset (q, 'd', 10); } @@ -90,26 +94,26 @@ void test2 (const H h) { char c; - strncpy (&c, str, 3); /* { dg-warning "will always overflow" "strncpy" } */ + strncpy (&c, str, 3); /* { dg-warning "specified bound 3 exceeds the size 1 of the destination" "strncpy" } */ struct { char b[4]; } x; - sprintf (x.b, "%s", "ABCD"); /* { dg-warning "will always overflow" "sprintf" } */ + sprintf (x.b, "%s", "ABCD"); /* { dg-warning "writing 5" "sprintf" } */ unsigned int i; - memcpy (&i, &h, sizeof (h)); /* { dg-warning "will always overflow" "memcpy" } */ + memcpy (&i, &h, sizeof (h)); /* { dg-warning "writing 16 " "memcpy" } */ unsigned char buf[21]; - memset (buf + 16, 0, 8); /* { dg-warning "will always overflow" "memset" } */ + memset (buf + 16, 0, 8); /* { dg-warning "writing 8 " "memset" } */ typedef struct { int i, j, k, l; } S; S *s[3]; - memset (s, 0, sizeof (S) * 3); /* { dg-warning "will always overflow" "memset" } */ + memset (s, 0, sizeof (S) * 3); /* { dg-warning "writing 48 " "memset" } */ struct T { char a[8]; char b[4]; char c[10]; } t; - stpcpy (t.c,"Testing..."); /* { dg-warning "will always overflow" "stpcpy" } */ + stpcpy (t.c,"Testing..."); /* { dg-warning "writing" "stpcpy" } */ char b1[7]; char b2[4]; memset (b1, 0, sizeof (b1)); - memset (b2, 0, sizeof (b1)); /* { dg-warning "will always overflow" "memset" } */ + memset (b2, 0, sizeof (b1)); /* { dg-warning "writing 7" "memset" } */ } diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c index 7c2bb609816..d537fb040c1 100644 --- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c @@ -6,7 +6,7 @@ /* { dg-options "-O2 -ftrack-macro-expansion=0" } */ #include "../gcc.c-torture/execute/builtins/chk.h" - + void *bar (int); extern void *malloc (__SIZE_TYPE__); @@ -115,7 +115,7 @@ baz (const struct A *x, const unsigned char *z) else do { - memcpy (e, d, 513); /* { dg-warning "will always overflow" "memcpy" } */ + memcpy (e, d, 513); /* { dg-warning "writing" "memcpy" } */ e += 4; } while (--h); diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c new file mode 100644 index 00000000000..4857bdaa73b --- /dev/null +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c @@ -0,0 +1,525 @@ +/* Test exercising buffer overflow warnings emitted for raw memory and + string manipulation builtins involving ranges of sizes and strings + of varying lengths. */ +/* { dg-do compile } */ +/* { dg-options "-O2 -ftrack-macro-expansion=0" } */ + +#define INT_MAX __INT_MAX__ +#define PTRDIFF_MAX __PTRDIFF_MAX__ +#define SIZE_MAX __SIZE_MAX__ + +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; + +static const size_t ssize_max = SIZE_MAX / 2; +static const size_t size_max = SIZE_MAX; + +extern signed char schar_val; +extern signed short sshrt_val; +extern signed int sint_val; +extern signed long slong_val; +extern unsigned char uchar_val; +extern unsigned short ushrt_val; +extern unsigned int uint_val; +extern unsigned long ulong_val; + +#define memcpy(d, s, n) (memcpy ((d), (s), (n)), sink ((d))) +extern void* (memcpy)(void*, const void*, size_t); + +#define mempcpy(d, s, n) (mempcpy ((d), (s), (n)), sink ((d))) +extern void* (mempcpy)(void*, const void*, size_t); + +#define memset(d, c, n) (memset ((d), (c), (n)), sink ((d))) +extern void* (memset)(void*, int, size_t); + +#define bzero(d, n) (bzero ((d), (n)), sink ((d))) +extern void (bzero)(void*, size_t); + +#define strcat(d, s) (strcat ((d), (s)), sink ((d))) +extern char* (strcat)(char*, const char*); + +#define strncat(d, s, n) (strncat ((d), (s), (n)), sink ((d))) +extern char* (strncat)(char*, const char*, size_t); + +#define strcpy(d, s) (strcpy ((d), (s)), sink ((d))) +extern char* (strcpy)(char*, const char*); + +#define strncpy(d, s, n) (strncpy ((d), (s), (n)), sink ((d))) +extern char* (strncpy)(char*, const char*, size_t); + +void sink (void*); + +/* Function to "generate" a random number each time it's called. Declared + (but not defined) and used to prevent GCC from making assumptions about + their values based on the variables uses in the tested expressions. */ +size_t random_unsigned_value (void); +ptrdiff_t random_signed_value (void); + +/* Return a random unsigned value between MIN and MAX. */ + +static inline size_t +unsigned_range (size_t min, size_t max) +{ + const size_t val = random_unsigned_value (); + return val < min || max < val ? min : val; +} + +/* Return a random signed value between MIN and MAX. */ + +static inline ptrdiff_t +signed_range (ptrdiff_t min, ptrdiff_t max) +{ + const ptrdiff_t val = random_signed_value (); + return val < min || max < val ? min : val; +} + +/* For brevity. */ +#define UR(min, max) unsigned_range (min, max) +#define SR(min, max) signed_range (min, max) + +/* UReturn a pointer to constant string whose length is at least MINLEN + and at most 10. */ +static inline const char* +string_range (size_t minlen) +{ + static const char str[] = "0123456789"; + + const size_t len = unsigned_range (minlen, sizeof str - 1); + + switch (len) + { + case 10: return "0123456789"; + case 9: return "012345678"; + case 8: return "01234567"; + case 7: return "0123456"; + case 6: return "012345"; + case 5: return "01234"; + case 4: return "0123"; + case 3: return "012"; + case 2: return "01"; + case 1: return "0"; + case 0: return ""; + } +} + +#define S(minlen) string_range (minlen) + +/* Test memcpy with a number of bytes bounded by a known range. */ + +void test_memcpy_range (void *d, const void *s) +{ + char buf[5]; + + memcpy (buf, s, UR (0, 5)); + memcpy (buf, s, UR (1, 5)); + memcpy (buf, s, UR (2, 5)); + memcpy (buf, s, UR (3, 5)); + memcpy (buf, s, UR (4, 5)); + + memcpy (buf, s, UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */ + + memcpy (buf + 5, s, UR (1, 2)); /* { dg-warning "writing between 1 and 2 bytes into a region of size 0 overflows the destination" } */ + + memcpy (buf + size_max, s, UR (1, 2)); /* { dg-warning "writing between 1 and 2 bytes into a region of size 0 overflows the destination" "excessive pointer offset" { xfail *-*-* } } */ + + memcpy (buf, s, UR (ssize_max, size_max)); /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 overflows the destination" } */ + memcpy (buf, s, UR (ssize_max + 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + memcpy (buf, s, UR (size_max - 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + + /* Exercise memcpy into a destination of unknown size with excessive + number of bytes. */ + memcpy (d, s, UR (ssize_max, size_max)); + memcpy (d, s, UR (ssize_max + 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + + memcpy (buf, s, SR (-1, 1)); + memcpy (buf, s, SR (-3, 2)); + memcpy (buf, s, SR (-5, 3)); + memcpy (buf, s, SR (-7, 4)); + memcpy (buf, s, SR (-9, 5)); + memcpy (buf, s, SR (-11, 6)); + + memcpy (d, s, SR (-1, 1)); + memcpy (d, s, SR (-3, 2)); + memcpy (d, s, SR (-5, 3)); + memcpy (d, s, SR (-7, 4)); + memcpy (d, s, SR (-9, 5)); + memcpy (d, s, SR (-11, 6)); + + memcpy (buf, s, SR (-2, -1)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + memcpy (d, s, SR (-2, -1)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + + /* Even though the following calls are bounded by the range of N's + type they must not cause a warning for obvious reasons. */ + memcpy (buf, s, schar_val); + memcpy (buf, s, sshrt_val); + memcpy (buf, s, sint_val); + memcpy (buf, s, slong_val); + + memcpy (buf, s, uchar_val); + memcpy (buf, s, ushrt_val); + memcpy (buf, s, uint_val); + memcpy (buf, s, ulong_val); + + memcpy (buf, s, schar_val + 1); + memcpy (buf, s, sshrt_val + 2); + memcpy (buf, s, sint_val + 3); + memcpy (buf, s, slong_val + 4); + + memcpy (d, s, uchar_val + 5); + memcpy (d, s, ushrt_val + 6); + memcpy (d, s, uint_val + 7); + memcpy (d, s, ulong_val + 8); + + memcpy (d, s, schar_val); + memcpy (d, s, sshrt_val); + memcpy (d, s, sint_val); + memcpy (d, s, slong_val); + + memcpy (d, s, uchar_val); + memcpy (d, s, ushrt_val); + memcpy (d, s, uint_val); + memcpy (d, s, ulong_val); + + memcpy (d, s, schar_val + 1); + memcpy (d, s, sshrt_val + 2); + memcpy (d, s, sint_val + 3); + memcpy (d, s, slong_val + 4); + + memcpy (d, s, uchar_val + 5); + memcpy (d, s, ushrt_val + 6); + memcpy (d, s, uint_val + 7); + memcpy (d, s, ulong_val + 8); +} + +/* Test mempcpy with a number of bytes bounded by a known range. */ + +void test_mempcpy_range (void *d, const void *s) +{ + char buf[5]; + + mempcpy (buf, s, UR (0, 5)); + mempcpy (buf, s, UR (1, 5)); + mempcpy (buf, s, UR (2, 5)); + mempcpy (buf, s, UR (3, 5)); + mempcpy (buf, s, UR (4, 5)); + + mempcpy (buf, s, UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */ + + mempcpy (buf, s, UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */ + + mempcpy (buf, s, UR (ssize_max, size_max)); /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 overflows the destination" } */ + mempcpy (buf, s, UR (ssize_max + 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + mempcpy (buf, s, UR (size_max - 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + + /* Exercise mempcpy into a destination of unknown size with excessive + number of bytes. */ + mempcpy (d, s, UR (ssize_max, size_max)); + mempcpy (d, s, UR (ssize_max + 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ +} + +/* Test memset with a number of bytes bounded by a known range. */ + +void test_memset_range (void *d) +{ + char buf[5]; + + memset (buf, 0, UR (0, 5)); + memset (buf, 0, UR (1, 5)); + memset (buf, 0, UR (2, 5)); + memset (buf, 0, UR (3, 5)); + memset (buf, 0, UR (4, 5)); + + memset (buf, 0, UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */ + + memset (buf, 0, UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */ + + memset (buf, 0, UR (ssize_max, size_max)); /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 overflows the destination" } */ + memset (buf, 0, UR (ssize_max + 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + memset (buf, 0, UR (size_max - 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + + /* Exercise memset into a destination of unknown size with excessive + number of bytes. */ + memset (d, 0, UR (ssize_max, size_max)); + memset (d, 0, UR (ssize_max + 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ +} + +/* Test bzero with a number of bytes bounded by a known range. */ + +void test_bzero_range (void *d) +{ + char buf[5]; + + bzero (buf, UR (0, 5)); + bzero (buf, UR (1, 5)); + bzero (buf, UR (2, 5)); + bzero (buf, UR (3, 5)); + bzero (buf, UR (4, 5)); + + bzero (buf, UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */ + + bzero (buf, UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */ + + bzero (buf, UR (ssize_max, size_max)); /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 overflows the destination" } */ + bzero (buf, UR (ssize_max + 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + bzero (buf, UR (size_max - 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + + /* Exercise bzero into a destination of unknown size with excessive + number of bytes. */ + bzero (d, UR (ssize_max, size_max)); + bzero (d, UR (ssize_max + 1, size_max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ +} + +/* Test strcat with an argument referencing a non-constant string of + lengths in a known range. */ + +void test_strcat_range (void) +{ + char buf[5] = ""; + + strcat (buf, S (0)); + strcat (buf, S (1)); + strcat (buf, S (2)); + strcat (buf, S (3)); + strcat (buf, S (4)); + strcat (buf, S (5)); /* { dg-warning "writing 6 bytes into a region of size 5 " } */ + + { + /* The implementation of the warning isn't smart enough to determine + the length of the string in the buffer so it assumes it's empty + and issues the warning basically for the same cases as strcat. */ + char buf2[5] = "12"; + strcat (buf2, S (4)); /* { dg-warning "writing 5 bytes into a region of size 3" "strcat to a non-empty string" { xfail *-*-* } } */ + } +} + +/* Verify that strcpy with an unknown source string doesn't cause + warnings unless the destination has zero size. */ + +void test_strcpy (const char *src) +{ + struct A { char a[2]; char b[3]; } a; + + strcpy (a.a, src); + strcpy (a.a + 1, src); + + /* There must be enough room in the destination for the terminating + nul, otherwise verify that a warning is issued. + The following works as expected with __builtin___strcpy_chk and + __builtin_object_size because they see that the offset is from + the a.a array. When optimization is enabled, it isn't detected + by __bultin_strcpy (when __builtin_object_size isn't called + explicitly) because by the time it's seen the offset has been + transformed to one from the beginning of the whole object, i.e., + as if it had been written as (char*)&a + 2 . Then the destination + size is taken to be the rest of the whole object. It is detected + by __builtin_strcpy when optimization is not enabled because then + the &a.a + 2 expression is preserved. But without optimization + an ordinary call to strcpy isn't transformed to __builtin_strcpy + and so it can't be detected here (since the rest of the test + relies on optimization). */ + strcpy (a.a + 2, src); /* { dg-warning "writing at least 1 byte into a region of size 0 " "strcpy into empty substring" { xfail *-*-* } } */ + + /* This does work. */ + strcpy (a.a + 5, src); /* { dg-warning "writing at least 1 byte into a region of size 0 " } */ + + /* As does this. */ + strcpy (a.a + 17, src); /* { dg-warning "writing at least 1 byte into a region of size 0 " } */ +} + +/* Test strcpy with a non-constant source string of length in a known + range. */ + +void test_strcpy_range (void) +{ + char buf[5]; + + strcpy (buf, S (0)); + strcpy (buf, S (1)); + strcpy (buf, S (2)); + strcpy (buf, S (4)); + strcpy (buf, S (5)); /* { dg-warning "writing 6 bytes into a region of size 5 " } */ + strcpy (buf, S (6)); /* { dg-warning "writing 7 bytes into a region of size 5 " } */ + strcpy (buf, S (7)); /* { dg-warning "writing 8 bytes into a region of size 5 " } */ + strcpy (buf, S (8)); /* { dg-warning "writing 9 bytes into a region of size 5 " } */ + strcpy (buf, S (9)); /* { dg-warning "writing 10 bytes into a region of size 5 " } */ + strcpy (buf, S (10)); /* { dg-warning "writing 11 bytes into a region of size 5 " } */ + + strcpy (buf + 5, S (0)); /* { dg-warning "writing 1 byte into a region of size 0 " } */ + + strcpy (buf + 17, S (0)); /* { dg-warning "writing 1 byte into a region of size 0 " } */ +} + +/* Test strncat with an argument referencing a non-constant string of + lengths in a known range. */ + +void test_strncat_range (void) +{ + char buf[5] = ""; + + strncat (buf, S (0), 0); + strncat (buf, S (0), 1); + strncat (buf, S (0), 2); + strncat (buf, S (0), 3); + strncat (buf, S (0), 4); + + strncat (buf + 5, S (0), 0); + + strncat (buf + 5, S (0), 1); /* { dg-warning "specified bound 1 exceeds the size 0 of the destination " } */ + strncat (buf + 5, S (1), 1); /* { dg-warning "specified bound 1 exceeds the size 0 of the destination " } */ + + /* Strncat always appends a terminating null after copying the N + characters so the following triggers a warning pointing out + that specifying sizeof(buf) as the upper bound may cause + the nul to overflow the destination. */ + strncat (buf, S (0), 5); /* { dg-warning "specified bound 5 equals the size of the destination" } */ + strncat (buf, S (0), 6); /* { dg-warning "specified bound 6 exceeds the size 5 of the destination" } */ + + strncat (buf, S (1), 0); + strncat (buf, S (1), 1); + strncat (buf, S (1), 2); + strncat (buf, S (1), 3); + strncat (buf, S (1), 4); + strncat (buf, S (1), 5); /* { dg-warning "specified bound 5 equals the size of the destination" } */ + strncat (buf, S (1), 6); /* { dg-warning "specified bound 6 exceeds the size 5 of the destination" } */ + strncat (buf, S (2), 6); /* { dg-warning "specified bound 6 exceeds the size 5 of the destination" } */ + + /* The following could just as well say "writing 6 bytes into a region + of size 5. Either would be correct and probably equally as clear + in this case. But when the length of the source string is not known + at all then the bound warning seems clearer. */ + strncat (buf, S (5), 6); /* { dg-warning "specified bound 6 exceeds the size 5 of the destination " } */ + strncat (buf, S (7), 6); /* { dg-warning "specified bound 6 exceeds the size 5 of the destination" } */ + + { + /* The implementation of the warning isn't smart enough to determine + the length of the string in the buffer so it assumes it's empty + and issues the warning basically for the same cases as strncpy. */ + char buf2[5] = "12"; + strncat (buf2, S (4), 4); /* { dg-warning "writing 5 bytes into a region of size 3" "strncat to a non-empty string" { xfail *-*-* } } */ + } +} + +/* Test strncat_chk with an argument referencing a non-constant string + of lengths in a known range. */ + +void test_strncat_chk_range (char *d) +{ + char buf[5] = ""; + +#define strncat_chk(d, s, n) \ + __builtin___strncat_chk ((d), (s), (n), __builtin_object_size (d, 1)); + + strncat_chk (buf, S (0), 1); + strncat_chk (buf, S (0), 2); + strncat_chk (buf, S (0), 3); + strncat_chk (buf, S (0), 4); + strncat_chk (buf, S (0), 5); /* { dg-warning "specified bound 5 equals the size of the destination " } */ + + strncat_chk (buf, S (5), 1); + strncat_chk (buf, S (5), 2); + strncat_chk (buf, S (5), 3); + strncat_chk (buf, S (5), 4); + strncat_chk (buf, S (5), 5); /* { dg-warning "specified bound 5 equals the size of the destination " } */ + + strncat_chk (buf, S (5), 10); /* { dg-warning "specified bound \[0-9\]+ exceeds the size 5 of the destination " } */ + + strncat_chk (d, S (5), size_max); /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size " } */ +} + +/* Test strncpy with a non-constant source string of length in a known + range and a constant number of bytes. */ + +void test_strncpy_string_range (char *d) +{ + char buf[5]; + + strncpy (buf, S (0), 0); + strncpy (buf, S (0), 1); + strncpy (buf, S (0), 2); + strncpy (buf, S (0), 3); + strncpy (buf, S (0), 4); + strncpy (buf, S (0), 5); + strncpy (buf, S (0), 6); /* { dg-warning "writing 6 bytes into a region of size 5 " } */ + + strncpy (buf, S (6), 4); + strncpy (buf, S (7), 5); + strncpy (buf, S (8), 6); /* { dg-warning "writing 6 bytes into a region of size 5 " } */ + + strncpy (buf, S (1), ssize_max - 1); /* { dg-warning "writing \[0-9\]+ bytes into a region of size 5" } */ + strncpy (buf, S (2), ssize_max); /* { dg-warning "writing \[0-9\]+ bytes into a region of size 5" } */ + strncpy (buf, S (3), ssize_max + 1); /* { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } */ + strncpy (buf, S (4), size_max); /* { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } */ + + /* Exercise strncpy into a destination of unknown size with a valid + and invalid constant number of bytes. */ + strncpy (d, S (1), ssize_max - 1); + strncpy (d, S (2), ssize_max); + strncpy (d, S (3), ssize_max + 1); /* { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } */ + strncpy (d, S (4), size_max); /* { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } */ +} + +/* Test strncpy with a non-constant source string of length in a known + range and a non-constant number of bytes also in a known range. */ + +void test_strncpy_string_count_range (char *dst, const char *src) +{ + char buf[5]; + + strncpy (buf, S (0), UR (0, 1)); + strncpy (buf, S (0), UR (0, 2)); + strncpy (buf, S (0), UR (0, 3)); + strncpy (buf, S (0), UR (0, 4)); + strncpy (buf, S (0), UR (0, 5)); + strncpy (buf, S (0), UR (0, 6)); + strncpy (buf, S (0), UR (1, 6)); + strncpy (buf, S (0), UR (2, 6)); + strncpy (buf, S (0), UR (3, 6)); + strncpy (buf, S (0), UR (4, 6)); + strncpy (buf, S (0), UR (5, 6)); + + strncpy (buf, S (9), UR (0, 1)); + strncpy (buf, S (8), UR (0, 2)); + strncpy (buf, S (7), UR (0, 3)); + strncpy (buf, S (6), UR (0, 4)); + strncpy (buf, S (8), UR (0, 5)); + strncpy (buf, S (7), UR (0, 6)); + strncpy (buf, S (6), UR (1, 6)); + strncpy (buf, S (5), UR (2, 6)); + strncpy (buf, S (9), UR (3, 6)); + strncpy (buf, S (8), UR (4, 6)); + strncpy (buf, S (7), UR (5, 6)); + + strncpy (buf, S (0), UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 " } */ + strncpy (buf, S (1), UR (7, 8)); /* { dg-warning "writing between 7 and 8 bytes into a region of size 5 " } */ + strncpy (buf, S (2), UR (ssize_max, ssize_max + 1)); /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 " } */ + + strncpy (buf, S (2), UR (ssize_max + 1, ssize_max + 2)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + + strncpy (buf + 5, S (0), UR (0, 1)); + strncpy (buf + 5, S (1), UR (0, 1)); + strncpy (buf + 5, S (0), UR (1, 2)); /* { dg-warning "writing between 1 and 2 bytes into a region of size 0 " } */ + strncpy (buf + 5, S (1), UR (1, 2)); /* { dg-warning "writing between 1 and 2 bytes into a region of size 0 " } */ + + strncpy (buf, src, UR (0, 1)); + strncpy (buf, src, UR (0, 2)); + strncpy (buf, src, UR (0, 3)); + strncpy (buf, src, UR (0, 4)); + strncpy (buf, src, UR (0, 5)); + strncpy (buf, src, UR (0, 6)); + strncpy (buf, src, UR (1, 6)); + strncpy (buf, src, UR (2, 6)); + strncpy (buf, src, UR (3, 6)); + strncpy (buf, src, UR (4, 6)); + strncpy (buf, src, UR (5, 6)); + strncpy (buf, src, UR (6, 7)); /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 " } */ + + /* Exercise strncpy into a destination of unknown size with a valid + and invalid constant number of bytes. */ + strncpy (dst, S (0), UR (5, 6)); + strncpy (dst, S (1), UR (6, 7)); + strncpy (dst, S (2), UR (7, 8)); + + strncpy (dst, S (3), UR (ssize_max, ssize_max + 1)); + + strncpy (dst, S (4), UR (ssize_max + 1, ssize_max + 2)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ +} diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c new file mode 100644 index 00000000000..489f88077d4 --- /dev/null +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c @@ -0,0 +1,260 @@ +/* Test exercising -Wrawmem-overflow and -Wstringop-overflow warnings. */ +/* { dg-do compile } */ +/* { dg-options "-O2 -Wstringop-overflow=1" } */ + +#define offsetof(type, mem) __builtin_offsetof (type, mem) + +/* Return the number of bytes from member MEM of TYPE to the end + of object OBJ. */ +#define offsetfrom(type, obj, mem) (sizeof (obj) - offsetof (type, mem)) + + +typedef __SIZE_TYPE__ size_t; +extern void* memcpy (void*, const void*, size_t); +extern void* memset (void*, int, __SIZE_TYPE__); + + +struct A { char a, b; }; +struct B { struct A a; char c, d; }; + +/* Function to call to "escape" pointers from tests below to prevent + GCC from assuming the values of the objects they point to stay + the unchanged. */ +void escape (void*, ...); + +/* Function to "generate" a random number each time it's called. Declared + (but not defined) and used to prevent GCC from making assumptions about + their values based on the variables uses in the tested expressions. */ +size_t random_unsigned_value (void); + +/* Return a random unsigned value between MIN and MAX. */ + +static inline size_t +range (size_t min, size_t max) +{ + const size_t val = random_unsigned_value (); + return val < min || max < val ? min : val; +} + +/* Verify that writing past the end of a local array is diagnosed. */ + +void test_memop_warn_local (const void *src) +{ + size_t n; + + n = range (8, 32); + + struct A a[2]; + + memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */ + escape (a, src); + + /* At -Wrawmem-overflow=1 the destination is considered to be + the whole array and its size is therefore sizeof a. */ + memcpy (&a[0], src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */ + escape (a, src); + + /* Verify the same as above but by writing into the first mmeber + of the first element of the array. */ + memcpy (&a[0].a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */ + escape (a, src); + + n = range (12, 32); + + struct B b[2]; + + memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" } */ + escape (b); + + /* The following idiom of clearing multiple members of a struct is + used in a few places in the Linux kernel. Verify that a warning + is issued for it when it writes past the end of the array object. */ + memset (&b[0].a.b, 0, offsetfrom (struct B, b, a.b) + 1); /* { dg-warning "writing 8 bytes into a region of size 7" } */ + escape (b); + + memset (&b->a.b, 0, offsetfrom (struct B, b, a.b) + 1); /* { dg-warning "writing 8 bytes into a region of size 7" } */ + escape (b); + + memset (&b[0].c, 0, offsetfrom (struct B, b, c) + 1); /* { dg-warning "writing 7 bytes into a region of size 6" } */ + escape (b); + + memset (&b->c, 0, offsetfrom (struct B, b, c) + 1); /* { dg-warning "writing 7 bytes into a region of size 6" } */ + escape (b); + + memset (&b[0].d, 0, offsetfrom (struct B, b, d) + 1); /* { dg-warning "writing 6 bytes into a region of size 5" } */ + escape (b); + + memset (&b->d, 0, offsetfrom (struct B, b, d) + 1); /* { dg-warning "writing 6 bytes into a region of size 5" } */ + escape (b); + + /* Same as above but clearing just elements of the second element + of the array. */ + memset (&b[1].a.b, 0, offsetfrom (struct B, b[1], a.b) + 1); /* { dg-warning "writing 4 bytes into a region of size 3" } */ + escape (b); + + memset (&b[1].c, 0, offsetfrom (struct B, b[1], c) + 1); /* { dg-warning "writing 3 bytes into a region of size 2" } */ + escape (b); + + memset (&b[1].d, 0, offsetfrom (struct B, b[1], d) + 1); /* { dg-warning "writing 2 bytes into a region of size 1" } */ + escape (b); +} + +/* Verify that writing past the end of a dynamically allocated array + of known size is diagnosed. */ + +void test_memop_warn_alloc (const void *src) +{ + size_t n; + + n = range (8, 32); + + struct A *a = __builtin_malloc (sizeof *a * 2); + + memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */ + escape (a, src); + + /* At -Wrawmem-overflow=1 the destination is considered to be + the whole array and its size is therefore sizeof a. */ + memcpy (&a[0], src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */ + escape (a, src); + + /* Verify the same as above but by writing into the first mmeber + of the first element of the array. */ + memcpy (&a[0].a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */ + escape (a, src); + + n = range (12, 32); + + struct B *b = __builtin_malloc (sizeof *b * 2); + + memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + /* The following idiom of clearing multiple members of a struct is + used in a few places in the Linux kernel. Verify that a warning + is issued for it when it writes past the end of the array object. */ + memset (&b[0].a.b, 0, offsetfrom (struct B, b, a.b) + 1); /* { dg-warning "writing 8 bytes into a region of size 7" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + memset (&b->a.b, 0, offsetfrom (struct B, b, a.b) + 1); /* { dg-warning "writing 8 bytes into a region of size 7" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + memset (&b[0].c, 0, offsetfrom (struct B, b, c) + 1); /* { dg-warning "writing 7 bytes into a region of size 6" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + memset (&b->c, 0, offsetfrom (struct B, b, c) + 1); /* { dg-warning "writing 7 bytes into a region of size 6" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + memset (&b[0].d, 0, offsetfrom (struct B, b, d) + 1); /* { dg-warning "writing 6 bytes into a region of size 5" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + memset (&b->d, 0, offsetfrom (struct B, b, d) + 1); /* { dg-warning "writing 6 bytes into a region of size 5" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + /* Same as above but clearing just elements of the second element + of the array. */ + memset (&b[1].a.b, 0, offsetfrom (struct B, b[1], a.b) + 1); /* { dg-warning "writing 4 bytes into a region of size 3" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + memset (&b[1].c, 0, offsetfrom (struct B, b[1], c) + 1); /* { dg-warning "writing 3 bytes into a region of size 2" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); + + memset (&b[1].d, 0, offsetfrom (struct B, b[1], d) + 1); /* { dg-warning "writing 2 bytes into a region of size 1" "memcpy into allocated" { xfail *-*-*} } */ + escape (b); +} + + +void test_memop_nowarn (const void *src) +{ + struct B b[2]; + + size_t n = range (sizeof b, 32); + + /* Verify that clearing the whole array is not diagnosed regardless + of whether the expression pointing to its beginning is obtained + from the array itself or its first member(s). */ + memcpy (b, src, n); + escape (b); + + memcpy (&b[0], src, n); + escape (b); + + memcpy (&b[0].a, src, n); + escape (b, src); + + memcpy (&b[0].a.a, src, n); + escape (b, src); + + /* Clearing multiple elements of an array of structs. */ + memset (&b[0].a.b, 0, sizeof b - offsetof (struct B, a.b)); + escape (b); + + memset (&b->a.b, 0, sizeof b - offsetof (struct B, a.b)); + escape (b); + + memset (&b[0].c, 0, sizeof b - offsetof (struct B, c)); + escape (b); + + memset (&b->c, 0, sizeof b - offsetof (struct B, c)); + escape (b); + + memset (&b[0].d, 0, sizeof b - offsetof (struct B, d)); + escape (b); + + memset (&b->d, 0, sizeof b - offsetof (struct B, d)); + escape (b); + + /* Same as above but clearing just elements of the second element + of the array. */ + memset (&b[1].a.b, 0, sizeof b[1] - offsetof (struct B, a.b)); + escape (b); + + memset (&b[1].c, 0, sizeof b[1] - offsetof (struct B, c)); + escape (b); + + memset (&b[1].d, 0, sizeof b[1] - offsetof (struct B, d)); + escape (b); +} + + +/* The foollowing function could specify in its API that it takes + an array of exactly two elements, as shown below. Verify that + writing into both elements is not diagnosed. */ +void test_memop_nowarn_arg (struct A[2], const void*); + +void test_memop_nowarn_arg (struct A *a, const void *src) +{ + memcpy (a, src, 2 * sizeof *a); + escape (a, src); + + memcpy (a, src, range (2 * sizeof *a, 123)); + escape (a, src); +} + + +struct C { char a[3], b; }; +struct D { struct C c; char d, e; }; + +extern char* strncpy (char*, const char*, __SIZE_TYPE__); + +void test_stringop_warn (void) +{ + size_t n = range (2 * sizeof (struct D) + 1, 33); + + struct C c[2]; + + /* Similarly, at -Wstringop-overflow=1 the destination is considered + to be the whole array and its size is therefore sizeof c. */ + strncpy (c[0].a, "123", n); /* { dg-warning "writing between 13 and 33 bytes into a region of size 8 overflows the destination" } */ + + escape (c); +} + + +void test_stringop_nowarn (void) +{ + struct D d[2]; + + strncpy (d[0].c.a, "123", range (sizeof d, 32)); + escape (d); +} diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-6.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-6.c new file mode 100644 index 00000000000..9572ce15cbb --- /dev/null +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-6.c @@ -0,0 +1,112 @@ +/* Test exercising -Wrawmem-overflow and -Wstringop-overflow warnings. */ +/* { dg-do compile } */ +/* { dg-options "-O2 -Wstringop-overflow=2" } */ + +#define offsetof(type, mem) __builtin_offsetof (type, mem) + +/* Return the number of bytes from member MEM of TYPE to the end + of object OBJ. */ +#define offsetfrom(type, obj, mem) (sizeof (obj) - offsetof (type, mem)) + + +typedef __SIZE_TYPE__ size_t; +extern void* memcpy (void*, const void*, size_t); +extern void* memset (void*, int, __SIZE_TYPE__); + + +struct A { char a, b; }; +struct B { struct A a; char c, d; }; + +/* Function to call to "escape" pointers from tests below to prevent + GCC from assuming the values of the objects they point to stay + the unchanged. */ +void escape (void*, ...); + +/* Function to "generate" a random number each time it's called. Declared + (but not defined) and used to prevent GCC from making assumptions about + their values based on the variables uses in the tested expressions. */ +size_t random_unsigned_value (void); + +/* Return a random unsigned value between MIN and MAX. */ + +static inline size_t +range (size_t min, size_t max) +{ + const size_t val = random_unsigned_value (); + return val < min || max < val ? min : val; +} + + +void test_memop_warn_object (const void *src) +{ + unsigned n = range (17, 29); + + struct A a[2]; + + /* At both -Wstringop-overflow=2, like at 1, the destination of functions + that operate on raw memory is considered to be the whole array and its + size is therefore sizeof a. */ + memcpy (&a[0], src, n); /* { dg-warning "writing between 17 and 29 bytes into a region of size 4 overflows the destination" } */ + escape (a); +} + +void test_memop_warn_subobject (const void *src) +{ + unsigned n = range (17, 31); + + struct B b[2]; + + /* At -Wrawmem-overflow=2 the destination is considered to be + the member sobobject of the first array element and its size + is therefore sizeof b[0].a. */ + memcpy (&b[0].a, src, n); /* { dg-warning "writing between 17 and 31 bytes into a region of size 8 overflows the destination" } */ + + escape (b); +} + +void test_memop_nowarn_subobject (void) +{ + struct B b[2]; + + /* The following idiom of clearing multiple members of a struct + has been seen in a few places in the Linux kernel. Verify + that a warning is not issued for it. */ + memset (&b[0].c, 0, sizeof b[0] - offsetof (struct B, c)); + + escape (b); +} + +struct C { char a[3], b; }; +struct D { struct C c; char d, e; }; + +extern char* strncpy (char*, const char*, __SIZE_TYPE__); + +void test_stringop_warn_object (const char *str) +{ + unsigned n = range (2 * sizeof (struct D), 32); + + struct C c[2]; + + /* Similarly, at -Wstringop-overflow=2 the destination is considered + to be the array member of the first element of the array c and its + size is therefore sizeof c[0].a. */ + strncpy (c[0].a, "123", n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 3 overflows the destination" } */ + escape (c); + + strncpy (c[0].a, str, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 3 overflows the destination" } */ + escape (c); +} + +void test_stringop_warn_subobject (const char *src) +{ + unsigned n = range (2 * sizeof (struct D), 32); + + struct D d[2]; + + /* Same as above. */ + strncpy (d[0].c.a, "123", n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 3 overflows the destination" } */ + escape (d); + + strncpy (d[0].c.a, src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 3 overflows the destination" } */ + escape (d); +} diff --git a/gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c b/gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c index 44677f16778..daff6801eea 100644 --- a/gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c +++ b/gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c @@ -24,15 +24,15 @@ test (int arg, ...) *p = 0; strncat (p, "abcdefghi", 10); *p = 0; - strncat (p, "abcdefghij", 10); /* { dg-warning "will always overflow" } */ + strncat (p, "abcdefghij", 10); /* { dg-warning "writing 11 bytes into a region of size 10 overflows the destination" } */ *p = 0; strncat (p, "abcdefgh", 11); *p = 0; - strncat (p, "abcdefghijkl", 11); /* { dg-warning "will always overflow" } */ + strncat (p, "abcdefghijkl", 11); /* { dg-warning "specified bound 11 exceeds the size 10 of the destination" } */ *p = 0; strncat (p, q, 9); *p = 0; - strncat (p, q, 10); /* { dg-warning "might overflow" } */ + strncat (p, q, 10); /* { dg-warning "specified bound 10 equals the size of the destination" } */ *p = 0; - strncat (p, q, 11); /* { dg-warning "might overflow" } */ + strncat (p, q, 11); /* { dg-warning "specified bound 11 exceeds the size 10 of the destination" } */ } diff --git a/gcc/testsuite/gcc.dg/fstack-protector-strong.c b/gcc/testsuite/gcc.dg/fstack-protector-strong.c index 8e9d891acea..94dc3508f1a 100644 --- a/gcc/testsuite/gcc.dg/fstack-protector-strong.c +++ b/gcc/testsuite/gcc.dg/fstack-protector-strong.c @@ -106,7 +106,7 @@ int foo8 () { char base[100]; - memcpy ((void *)base, (const void *)pg0, 105); + memcpy ((void *)base, (const void *)pg0, 105); /* { dg-warning "writing 105 bytes into a region of size 100" } */ return (int)(base[32]); } diff --git a/gcc/testsuite/gcc.dg/memcpy-2.c b/gcc/testsuite/gcc.dg/memcpy-2.c index 24464abd4a9..7f839d27abd 100644 --- a/gcc/testsuite/gcc.dg/memcpy-2.c +++ b/gcc/testsuite/gcc.dg/memcpy-2.c @@ -7,7 +7,7 @@ typedef __SIZE_TYPE__ size_t; extern inline __attribute__((gnu_inline, always_inline, artificial)) void * memcpy (void *__restrict dest, const void *__restrict src, size_t len) { - return __builtin___memcpy_chk (dest, /* { dg-warning "will always overflow destination buffer" } */ + return __builtin___memcpy_chk (dest, /* { dg-warning "writing" } */ src, len, __builtin_object_size (dest, 0)); } diff --git a/gcc/testsuite/gcc.dg/pr40340-1.c b/gcc/testsuite/gcc.dg/pr40340-1.c index aae84c63781..78540a25212 100644 --- a/gcc/testsuite/gcc.dg/pr40340-1.c +++ b/gcc/testsuite/gcc.dg/pr40340-1.c @@ -20,5 +20,5 @@ main (void) return 0; } -/* { dg-warning "will always overflow destination buffer" "" { target *-*-* } 10 } */ +/* { dg-warning "writing" "" { target *-*-* } 10 } */ /* { dg-message "file included" "In file included" { target *-*-* } 0 } */ diff --git a/gcc/testsuite/gcc.dg/pr40340-2.c b/gcc/testsuite/gcc.dg/pr40340-2.c index a0d6e084e73..1dc21d1bb62 100644 --- a/gcc/testsuite/gcc.dg/pr40340-2.c +++ b/gcc/testsuite/gcc.dg/pr40340-2.c @@ -12,5 +12,5 @@ main (void) return 0; } -/* { dg-warning "will always overflow destination buffer" "" { target *-*-* } 10 } */ +/* { dg-warning "writing" "" { target *-*-* } 10 } */ /* { dg-message "file included" "In file included" { target *-*-* } 0 } */ diff --git a/gcc/testsuite/gcc.dg/pr40340-5.c b/gcc/testsuite/gcc.dg/pr40340-5.c index f50514c3ac9..e5171473716 100644 --- a/gcc/testsuite/gcc.dg/pr40340-5.c +++ b/gcc/testsuite/gcc.dg/pr40340-5.c @@ -13,5 +13,5 @@ main (void) return 0; } -/* { dg-warning "will always overflow destination buffer" "" { target *-*-* } 10 } */ +/* { dg-warning "writing" "" { target *-*-* } 10 } */ /* { dg-message "file included" "In file included" { target *-*-* } 0 } */ diff --git a/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c b/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c index 7ce9eaeb261..b5a59f4d0ba 100644 --- a/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c +++ b/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c @@ -710,4 +710,4 @@ f4 (char *x, char **y, int z, char w[64]) return z; } -/* { dg-prune-output "\[\n\r\]*will always overflow\[\n\r\]*" } */ +/* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */ diff --git a/gcc/testsuite/gcc.dg/torture/pr71132.c b/gcc/testsuite/gcc.dg/torture/pr71132.c index 2991718ad3a..2544eb14292 100644 --- a/gcc/testsuite/gcc.dg/torture/pr71132.c +++ b/gcc/testsuite/gcc.dg/torture/pr71132.c @@ -1,4 +1,9 @@ /* { dg-do compile } */ +/* { dg-additional-options "-Wno-stringop-overflow" } */ +/* The loop below writes past the end of the global object a. + When the loop is transformed into a call to memcpy the buffer + overflow is detected and diagnosed by the -Wstringop-overflow + option enabled by default. */ typedef unsigned size_t; struct { diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c index fae584eae1e..a551e23c251 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c @@ -1,5 +1,5 @@ /* { dg-do compile } */ -/* { dg-options "-std=c99 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ +/* { dg-options "-Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ /* { dg-require-effective-target int32plus } */ /* When debugging, define LINE to the line number of the test case to exercise @@ -1456,9 +1456,7 @@ void test_vsprintf_chk_int (__builtin_va_list va) void test_snprintf_c_const (char *d) { - T (-1, "%c", 0); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ - - __builtin_snprintf (d, INT_MAX, "%c", 0); /* { dg-warning "specified destination size 2147483647 is too large" "ilp32" { target { ilp32 } } } */ + T (-1, "%c", 0); /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */ /* Verify the full text of the diagnostic for just the distinct messages and use abbreviations in subsequent test cases. */ @@ -1506,9 +1504,9 @@ void test_snprintf_chk_c_const (void) /* Verify that specifying a size of the destination buffer that's bigger than its actual size (normally determined and passed to the function by __builtin_object_size) is diagnosed. */ - __builtin___snprintf_chk (buffer, 3, 0, 2, " "); /* { dg-warning "always overflow|specified size 3 exceeds the size 2 of the destination" } */ + __builtin___snprintf_chk (buffer, 3, 0, 2, " "); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" } */ - T (-1, "%c", 0); /* { dg-warning "specified destination size \[^ \]* is too large" } */ + T (-1, "%c", 0); /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */ T (0, "%c", 0); T (0, "%c%c", 0, 0); @@ -1619,7 +1617,7 @@ void test_vsprintf_int (__builtin_va_list va) void test_vsnprintf_s (__builtin_va_list va) { - T (-1, "%s"); /* { dg-warning "specified destination size \[^ \]* is too large" } */ + T (-1, "%s"); /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */ T (0, "%s"); T (1, "%s"); @@ -1642,9 +1640,9 @@ void test_vsnprintf_chk_s (__builtin_va_list va) /* Verify that specifying a size of the destination buffer that's bigger than its actual size (normally determined and passed to the function by __builtin_object_size) is diagnosed. */ - __builtin___vsnprintf_chk (buffer, 123, 0, 122, "%-s", va); /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */ + __builtin___vsnprintf_chk (buffer, 123, 0, 122, "%-s", va); /* { dg-warning "specified bound 123 exceeds the size 122 of the destination" } */ - __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va); /* { dg-warning "always overflow|destination size .\[0-9\]+. is too large" } */ + __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va); /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */ T (0, "%s"); T (1, "%s"); diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c index 00176ed056e..57fea66d69e 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c @@ -1,5 +1,5 @@ /* { dg-do compile } */ -/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ +/* { dg-options "-O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ typedef __SIZE_TYPE__ size_t; @@ -248,34 +248,34 @@ void test_too_large (char *d, int x, __builtin_va_list va) const size_t imax = __INT_MAX__; const size_t imax_p1 = imax + 1; - __builtin_snprintf (d, imax, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ is too large" "INT_MAX" { target ilp32 } } */ - __builtin_snprintf (d, imax_p1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "INT_MAX + 1" { target lp64 } } */ - /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + __builtin_snprintf (d, imax, "%c", x); + __builtin_snprintf (d, imax_p1, "%c", x); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "INT_MAX + 1" { target lp64 } } */ + /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size" "INT_MAX + 1" { target { ilp32 } } .-1 } */ - __builtin_vsnprintf (d, imax, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ is too large" "INT_MAX" { target ilp32 } } */ - __builtin_vsnprintf (d, imax_p1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "INT_MAX + 1" { target lp64 } } */ - /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + __builtin_vsnprintf (d, imax, "%c", va); + __builtin_vsnprintf (d, imax_p1, "%c", va); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "INT_MAX + 1" { target lp64 } } */ + /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size" "INT_MAX + 1" { target { ilp32 } } .-1 } */ - __builtin___snprintf_chk (d, imax, 0, imax, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ is too large" "INT_MAX" { target ilp32 } } */ - __builtin___snprintf_chk (d, imax_p1, 0, imax_p1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "INT_MAX + 1" { target lp64 } } */ - /* { dg-warning "specified destination size \[0-9\]+ is too large" "INT_MAX + 1" { target { ilp32 } } .-1 } */ + __builtin___snprintf_chk (d, imax, 0, imax, "%c", x); + __builtin___snprintf_chk (d, imax_p1, 0, imax_p1, "%c", x); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "INT_MAX + 1" { target lp64 } } */ + /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size" "INT_MAX + 1" { target { ilp32 } } .-1 } */ - __builtin___vsnprintf_chk (d, imax, 0, imax, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ is too large" "INT_MAX" { target ilp32 } } */ - __builtin___vsnprintf_chk (d, imax_p1, 0, imax_p1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "INT_MAX + 1" { target lp64 } } */ - /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + __builtin___vsnprintf_chk (d, imax, 0, imax, "%c", va); + __builtin___vsnprintf_chk (d, imax_p1, 0, imax_p1, "%c", va); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "INT_MAX + 1" { target lp64 } } */ + /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size" "INT_MAX + 1" { target { ilp32 } } .-1 } */ const size_t ptrmax = __PTRDIFF_MAX__; const size_t ptrmax_m1 = ptrmax - 1; - __builtin_snprintf (d, ptrmax_m1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX - 1" { target lp64 } } */ - __builtin_snprintf (d, ptrmax, " %c", x); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + __builtin_snprintf (d, ptrmax_m1, "%c", x); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX - 1" { target lp64 } } */ + __builtin_snprintf (d, ptrmax, " %c", x); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX" { target lp64 } } */ - __builtin_vsnprintf (d, ptrmax_m1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX - 1" { target lp64 } } */ - __builtin_vsnprintf (d, ptrmax, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + __builtin_vsnprintf (d, ptrmax_m1, "%c", va); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX - 1" { target lp64 } } */ + __builtin_vsnprintf (d, ptrmax, "%c", va); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX" { target lp64 } } */ - __builtin___snprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX - 1" { target lp64 } } */ - __builtin___snprintf_chk (d, ptrmax, 0, ptrmax, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + __builtin___snprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", x); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX - 1" { target lp64 } } */ + __builtin___snprintf_chk (d, ptrmax, 0, ptrmax, "%c", x); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX" { target lp64 } } */ - __builtin___vsnprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX - 1" { target lp64 } } */ - __builtin___vsnprintf_chk (d, ptrmax, 0, ptrmax, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + __builtin___vsnprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", va); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX - 1" { target lp64 } } */ + __builtin___vsnprintf_chk (d, ptrmax, 0, ptrmax, "%c", va); /* { dg-warning "specified bound \[0-9\]+ exceeds .INT_MAX." "PTRDIFF_MAX" { target lp64 } } */ } diff --git a/gcc/testsuite/gfortran.dg/char_length_3.f90 b/gcc/testsuite/gfortran.dg/char_length_3.f90 index 97f7fb4c076..6529a77ff04 100644 --- a/gcc/testsuite/gfortran.dg/char_length_3.f90 +++ b/gcc/testsuite/gfortran.dg/char_length_3.f90 @@ -47,3 +47,6 @@ y(1) = 'hello world' end subroutine end + + ! Remove -Wstringop-overflow warnings. + ! { dg-prune-output "overflows the destination" } diff --git a/gcc/testsuite/gfortran.dg/pr38868.f b/gcc/testsuite/gfortran.dg/pr38868.f index a8c4469f089..583bf901b4e 100644 --- a/gcc/testsuite/gfortran.dg/pr38868.f +++ b/gcc/testsuite/gfortran.dg/pr38868.f @@ -9,7 +9,7 @@ ANER(1)='A ' ANER(2)=' ' LINE=' ' - LINE(78:80)='xyz' + LINE(78:80)='xyz' ! { dg-warning "writing 3 bytes into a region of size 2" } WRITE(*,'(A82)') "'"//LINE//"'" END -- 2.30.2