From patchwork Fri Nov 4 20:16:42 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Sebor X-Patchwork-Id: 80919 Delivered-To: patch@linaro.org Received: by 10.140.97.165 with SMTP id m34csp159159qge; Fri, 4 Nov 2016 13:17:21 -0700 (PDT) X-Received: by 10.99.208.21 with SMTP id z21mr24246752pgf.79.1478290641457; Fri, 04 Nov 2016 13:17:21 -0700 (PDT) Return-Path: Received: from sourceware.org (server1.sourceware.org. [209.132.180.131]) by mx.google.com with ESMTPS id 127si18380133pgg.233.2016.11.04.13.17.21 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 04 Nov 2016 13:17:21 -0700 (PDT) Received-SPF: pass (google.com: domain of gcc-patches-return-440497-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-440497-patch=linaro.org@gcc.gnu.org designates 209.132.180.131 as permitted sender) smtp.mailfrom=gcc-patches-return-440497-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=coGH4RZVs7F940VEm QFL1ySjmqIC/hn7Oo/Bc6yvZgs0vadkd+4apnM6hD0eWmBH+/66bJkFnPjPlMJKX XtxxVVo2SdCNwktYBEv1SsVhsJkJSr/pH3yzk2ZGa+nFCsQLRO/PvOsbdXvQAyem 8j6FuVjPvPPgG1YTkWa9nRNunE= 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=2WaTJOOjS1HbMECOSiuzxsk Sznc=; b=qo6xr4cRiSSVBVFJQRD8ppvxroqzDj/a8yvDYRXQrR0Unyopr2Oj7mK 0VxSDBSX+m2X4hH0S+NSzzuHkFLdFATEfx8/j7R8xI+JwRgqTsYCvuJRnddgy+tq clm8qg8WV3mlgwEAV2EBIDEAYH6qPIB7gs5CqCxT5HA2asnzOxNY= Received: (qmail 102675 invoked by alias); 4 Nov 2016 20:16:59 -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 102661 invoked by uid 89); 4 Nov 2016 20:16:58 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=0.4 required=5.0 tests=AWL, BAYES_50, FREEMAIL_FROM, RCVD_IN_DNSWL_LOW, RCVD_IN_SORBS_SPAM, SPF_PASS autolearn=ham version=3.3.2 spammy=clearing, random, 67, sk:fold_co X-HELO: mail-qt0-f195.google.com Received: from mail-qt0-f195.google.com (HELO mail-qt0-f195.google.com) (209.85.216.195) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Fri, 04 Nov 2016 20:16:48 +0000 Received: by mail-qt0-f195.google.com with SMTP id m48so3292085qta.2 for ; Fri, 04 Nov 2016 13:16:47 -0700 (PDT) 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=Qu5UafHmPYr2eANFTNuwUTh6iuG74EZ+kHDVc2No30U=; b=ZMnpkohBxfO7+O2nqKXUaxoo9eXmQxnPvjJ0Duo5PvfrC2mfUEROkMR4qUXU7C2Elj LXnV3MmUVhK3ELSXnQTNKI5v3xjtuP+rexkJZfsZhqaMUPLekAuNhlv//l6SoJtVvYkh wo6saUaC98vTq1feVWoYsa9/NVQTO0IW3Wu77SDe7rCz/1dhELhVJCcWOOqni7FmiUhf qjUyljMWJ4j1OJT28djrO9TvqNd3EQkIAvVMNqVxKNdUQqAd8moK5Lt0yCrNDo7JrRc3 5iD/yeTxRQtfffMda3GCoPkk2N62dq8t+NXtacVNAx0yYKUlfLRu8BY1beyIkros5epO hBDA== X-Gm-Message-State: ABUngvduIQdpe0C1aACMaJECoq9wQg8ekmQeo7vQjs730t7uLupUJS1vo/4SEedFtyNqzA== X-Received: by 10.237.59.230 with SMTP id s35mr16078013qte.37.1478290606288; Fri, 04 Nov 2016 13:16:46 -0700 (PDT) Received: from [192.168.0.26] (71-212-250-41.hlrn.qwest.net. [71.212.250.41]) by smtp.gmail.com with ESMTPSA id v127sm868632qkb.29.2016.11.04.13.16.43 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 04 Nov 2016 13:16:45 -0700 (PDT) Subject: Re: [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> From: Martin Sebor Message-ID: <852bbe4d-3fa5-7a42-a51d-0b73bc745ffc@gmail.com> Date: Fri, 4 Nov 2016 14:16:42 -0600 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: <20161102193230.GZ3541@tucnak.redhat.com> X-IsSubscribed: yes 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/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/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. * doc/invoke.texi (Warning Options): Document -Wstringop-overflow. gcc/testsuite/ChangeLog: PR c/53562 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/fstack-protector-strong.c: Add expected diagnostic. diff --git a/gcc/builtins.c b/gcc/builtins.c index cc711a0..e2cf20a 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,292 @@ expand_builtin_memcpy_args (tree dest, tree src, tree len, rtx target, tree exp) return dest_addr; } +/* Fill the 2-element RANGE array with the minimum and maximum values + EXP is known to have and return true, otherwise null and return + false. */ + +static bool +get_size_range (tree exp, tree range[2]) +{ + if (tree_fits_uhwi_p (exp)) + { + range[0] = range[1] = exp; + return true; + } + + if (TREE_CODE (exp) == SSA_NAME) + { + wide_int min, max; + enum value_range_type range_type = get_range_info (exp, &min, &max); + + if (range_type == VR_RANGE) + { + /* Interpret the bound in the variable's type. */ + range[0] = wide_int_to_tree (TREE_TYPE (exp), min); + range[1] = wide_int_to_tree (TREE_TYPE (exp), max); + return true; + } + else if (range_type == VR_ANTI_RANGE) + { + /* 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. (This is way too permissive.) */ + tree maxobjsize = build_int_cst (sizetype, HOST_WIDE_INT_MAX); + + 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)) + { + if (objsize != maxobjsize && tree_int_cst_lt (objsize, range[0])) + { + location_t loc = tree_nonartificial_location (exp); + + 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; + } + else if (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 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; + } + } + } + + 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 +3310,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 +3365,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 +3550,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 +3585,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 +3706,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 +3844,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 +3958,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 +4146,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 +6693,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 +9621,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 +9722,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 +9837,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/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 {