From patchwork Wed Nov 9 22:49:50 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Sebor X-Patchwork-Id: 81579 Delivered-To: patch@linaro.org Received: by 10.140.97.165 with SMTP id m34csp442372qge; Wed, 9 Nov 2016 14:50:40 -0800 (PST) X-Received: by 10.99.136.194 with SMTP id l185mr18526068pgd.106.1478731840848; Wed, 09 Nov 2016 14:50:40 -0800 (PST) Return-Path: Received: from sourceware.org (server1.sourceware.org. [209.132.180.131]) by mx.google.com with ESMTPS id i75si1492959pfj.285.2016.11.09.14.50.40 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 09 Nov 2016 14:50:40 -0800 (PST) Received-SPF: pass (google.com: domain of gcc-patches-return-440910-patch=linaro.org@gcc.gnu.org designates 209.132.180.131 as permitted sender) client-ip=209.132.180.131; Authentication-Results: mx.google.com; dkim=pass header.i=@gcc.gnu.org; spf=pass (google.com: domain of gcc-patches-return-440910-patch=linaro.org@gcc.gnu.org designates 209.132.180.131 as permitted sender) smtp.mailfrom=gcc-patches-return-440910-patch=linaro.org@gcc.gnu.org; dmarc=fail (p=NONE dis=NONE) header.from=gmail.com DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender :subject:to:references:from:message-id:date:mime-version :in-reply-to:content-type; q=dns; s=default; b=VdRNgWikl2gTGGVeC qkt+9y6H7Dupz6unvLU7lA/WpeVPwSkC3y3f1C6B5V63KHQXmQZBQ9gFKnEuHwj7 rqZbgpqNSq7RHPW3JPtz91GxQWxfUKbX1LJ/J55XBmWGbnME3NBVTZtnXQWS5CWu BTi8ITb0C0KzV//4b3OnnpVgdU= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender :subject:to:references:from:message-id:date:mime-version :in-reply-to:content-type; s=default; bh=at8Z1MguqtxfUceO2dfrOr7 H4D8=; b=PDxhUrKDfY84wSGw6bfBM72VnUsRx8HlTuVbDJ/PuimmZQQ0khp3Anf xSqzzn1hj/aKP+X2pe4+dBhOZpqGru3ejmuNWj5ow7LHyx93aY2s3amGlRQVqFQ6 LrcUigP3drh5zhlTiSqpK4m898cIQYfqGLdY4ec9mx7mLpuxJ6J0= Received: (qmail 102030 invoked by alias); 9 Nov 2016 22:50:07 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 101998 invoked by uid 89); 9 Nov 2016 22:50:07 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=0.6 required=5.0 tests=AWL, BAYES_50, FREEMAIL_FROM, RCVD_IN_DNSWL_NONE, SPF_PASS autolearn=ham version=3.3.2 spammy=Color, yellow, validated, xb X-HELO: mail-qk0-f176.google.com Received: from mail-qk0-f176.google.com (HELO mail-qk0-f176.google.com) (209.85.220.176) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Wed, 09 Nov 2016 22:49:56 +0000 Received: by mail-qk0-f176.google.com with SMTP id q130so275410693qke.1 for ; Wed, 09 Nov 2016 14:49:55 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:subject:to:references:from:message-id:date :user-agent:mime-version:in-reply-to; bh=OHKzZ2CrS+VmWtAwZOwyN5wDvbesv8TAbPMY98LVDUo=; b=f0rXOYY3GLPyrOovyTK+6Qvgki/o6AOz3AJni+LmELJd35KX5MDatgkjhAqJWPuSfm 5XmElBzvl2IW6sfhW7MkTCG+J67FD5wef0F/MTfWZiAt1IfwMu+VaPe9OFO3i2oou/m0 UKJfAIzeGS0KnhM19MJ9YN966mE8R2hmv8jHL5RN3tRt8LNKprn/jJS7PE2R4Pw534qd SLMIZNFsE++qTy1LUflRIwO+d97/RgvLMOKmcN//0Qy+1H7DJXmHSGJbVydw74A4fwaX 9Vsgfu1u2JRb6D7t4AhPIZ30ppUvYB4SqPXHc5x6Bf3ihSVLoXhXMaHdA6nhq9EsBycg wTPw== X-Gm-Message-State: ABUngvdD4v1tlHJ3Uj8y4GzF6+TQgmssjx5YJBMNPxsKZ2Ine2tv7cMn7uNtWqHd7WjQ+A== X-Received: by 10.55.24.36 with SMTP id j36mr2633930qkh.268.1478731794208; Wed, 09 Nov 2016 14:49:54 -0800 (PST) Received: from [192.168.0.26] (75-166-206-79.hlrn.qwest.net. [75.166.206.79]) by smtp.gmail.com with ESMTPSA id y200sm976562qkb.37.2016.11.09.14.49.51 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 09 Nov 2016 14:49:53 -0800 (PST) Subject: [PING] [PATCH] enhance buffer overflow warnings (and c/53562) To: Jakub Jelinek , Jeff Law , gcc-patches@gcc.gnu.org, Tobias Burnus References: <20161031123909.GA9233@physik.fu-berlin.de> <334666bc-6308-aa5f-f63f-40697695152f@gmail.com> <904d9d3b-8662-e714-cc82-e08c72c54c0e@gmail.com> <20161101141025.GR3541@tucnak.redhat.com> <20161101191420.GZ3541@tucnak.redhat.com> <6acf4a8f-1451-d58a-900c-833f6dc2e21a@gmail.com> <20161102073759.GG3541@tucnak.redhat.com> <4745f128-62a1-ead9-cdf8-f26b18f8051a@gmail.com> <20161102193230.GZ3541@tucnak.redhat.com> <852bbe4d-3fa5-7a42-a51d-0b73bc745ffc@gmail.com> From: Martin Sebor Message-ID: Date: Wed, 9 Nov 2016 15:49:50 -0700 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.3.0 MIME-Version: 1.0 In-Reply-To: <852bbe4d-3fa5-7a42-a51d-0b73bc745ffc@gmail.com> X-IsSubscribed: yes The attached minor update to the patch also resolves bug 77784 that points out that -Wformat-length issues a warning also issued during the expansion of some of the __builtin___sprintf_chk intrinsics. Martin On 11/04/2016 02:16 PM, Martin Sebor wrote: > Attached is an update to the patch that takes into consideration > the feedback I got. It goes back to adding just one option, > -Wstringop-overflow, as in the original, while keeping the Object > Size type as an argument. It uses type-1 as the default setting > for string functions (strcpy et al.) and, unconditionally, type-0 > for raw memory functions (memcpy, etc.) > > I retested Binutils 2.27 and the Linux kernel again with this patch > and also added Glibc, and it doesn't complain about anything (both > Binutils and the kernel also build cleanly with an unpatched GCC > with_FORTIFY_SOURCE=2 or its rough equivalent for the kernel). > The emit-rtl.c warning (bug 78174) has also been suppressed by > the change to bos type-0 for memcpy. > > While the patch doesn't trigger any false positives (AFAIK) it is > subject to a fair number of false negatives due to the limitations > of the tree-object-size pass, and due to transformations done by > other passes that prevent it from detecting some otherwise obvious > overflows. Although unfortunate, I believe the warnings that are > emitted are useful as the first line of defense in software that > doesn't use _FORTIFY_SOURCE (such as GCC itself). And this can > of course be improved if some of the limitations are removed over > time. > > Martin 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 * c.opt (-Wstringop-overflow): New option. gcc/ChangeLog: PR c/53562 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 c/53562 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. * gcc.dg/fstack-protector-strong.c: Add expected diagnostic. diff --git a/gcc/builtins.c b/gcc/builtins.c index cc711a0..cb7bbaf 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); @@ -3011,6 +3013,295 @@ 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) + { + /* An anti-range implies the original variable is signed and + its lower bound is negative and the upper bound positive. + Since that means that the expression's value could be zero + nothing interesting can be inferred from this. */ + } + } + + 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 verfified 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%D 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%D 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%D 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%D writing %wu byte into a region " + "of size %wu overflows the destination") + : G_("%K%D 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%D 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%D 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%D 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%D 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%D 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 @@ -3022,13 +3313,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. @@ -3076,15 +3368,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. @@ -3256,6 +3553,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 srouce 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 @@ -3264,13 +3588,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 @@ -3378,6 +3709,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. */ @@ -3391,9 +3847,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; @@ -3481,13 +3961,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. @@ -3668,20 +4149,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), @@ -6214,12 +6696,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) @@ -9130,22 +9624,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) @@ -9231,68 +9725,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 @@ -9346,10 +9840,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/c.opt b/gcc/c-family/c.opt index 7d8a726..d92fca2 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -665,6 +665,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 ad9304f..781caba 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -292,7 +292,8 @@ Objective-C and Objective-C++ Dialects}. -Wparentheses -Wno-pedantic-ms-format @gol -Wplacement-new -Wplacement-new=@var{n} @gol -Wpointer-arith -Wno-pointer-to-int-cast @gol --Wno-pragmas -Wredundant-decls -Wno-return-local-addr @gol +-Wno-pragmas -Wredundant-decls @gol +-Wno-return-local-addr @gol -Wreturn-type -Wsequence-point -Wshadow -Wno-shadow-ivar @gol -Wshadow=global, -Wshadow=local, -Wshadow=compatible-local @gol -Wshift-overflow -Wshift-overflow=@var{n} @gol @@ -302,6 +303,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 @@ -4908,6 +4910,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 3138ad3..5f26320 100644 --- a/gcc/gimple-ssa-sprintf.c +++ b/gcc/gimple-ssa-sprintf.c @@ -2701,10 +2701,15 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator gsi) /* 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. */ - if (dstsize >= target_size_max () / 2) + 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. */ + && (idx_objsize == HOST_WIDE_INT_M1U + || !warn_stringop_overflow)) warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, - "specified destination size %wu too large", - dstsize); + "specified bound %wu exceeds maximum object size %wu", + dstsize, target_size_max () / 2); } else if (TREE_CODE (size) == SSA_NAME) { @@ -2747,10 +2752,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/c-c++-common/Wsizeof-pointer-memaccess2.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c index d9ec7e2..9a02373 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 09263e5..0207f9a 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 ebafc99..d67d6bf 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 f291345..e2b1dd2 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 8b5c33e..2e6189b 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 0e99568..a216f47 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 cffbcfc..7708f21 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 291cfb9..211e068 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 e8129ce..f50ba7c 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 e491ff5..7689287 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 7c2bb60..d537fb0 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 0000000..43e2a99 --- /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), size_max); /* { 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 0000000..489f880 --- /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 0000000..9572ce1 --- /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 44677f1..daff680 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 8e9d891..94dc350 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 24464abd..7f839d2 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 aae84c6..78540a2 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 a0d6e084..1dc21d1 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 f50514c..e517147 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 7ce9eae..b5a59f4 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 2991718..2544eb1 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 5779a95..c8c1c28 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 @@ -1285,7 +1285,7 @@ void test_vsprintf_chk_int (__builtin_va_list va) void test_snprintf_c_const (void) { - T (-1, "%c", 0); /* { dg-warning "specified destination size \[0-9\]+ too large" } */ + 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. */ @@ -1333,9 +1333,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 \[^ \]* 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); @@ -1446,7 +1446,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 \[^ \]* too large" } */ + T (-1, "%s"); /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */ T (0, "%s"); T (1, "%s"); @@ -1469,9 +1469,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\]+. 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");