From patchwork Tue Nov 1 02:39:15 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Martin Sebor X-Patchwork-Id: 80302 Delivered-To: patch@linaro.org Received: by 10.140.97.247 with SMTP id m110csp421860qge; Mon, 31 Oct 2016 19:39:55 -0700 (PDT) X-Received: by 10.98.206.207 with SMTP id y198mr23154127pfg.70.1477967995308; Mon, 31 Oct 2016 19:39:55 -0700 (PDT) Return-Path: Received: from sourceware.org (server1.sourceware.org. [209.132.180.131]) by mx.google.com with ESMTPS id da4si23118112pad.260.2016.10.31.19.39.54 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 31 Oct 2016 19:39:55 -0700 (PDT) Received-SPF: pass (google.com: domain of gcc-patches-return-440018-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-440018-patch=linaro.org@gcc.gnu.org designates 209.132.180.131 as permitted sender) smtp.mailfrom=gcc-patches-return-440018-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:cc:message-id:date:mime-version :in-reply-to:content-type; q=dns; s=default; b=vPwQihxejoBV8cQGD KQ9t0qYCTzQCo3PCYHgUElJotYp+4J1+9lq2/E8mfta0ysAB6NOQHsBwqw01eS2p Ynjt4aj85nOkF1UxdW/PzcjMTAJyrtMugbz4uU8aKgnElO4LAWo/oKXg6AGMoh/l GAJQqc2ilzYLnwl1ncQ/VWkrsc= 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:cc:message-id:date:mime-version :in-reply-to:content-type; s=default; bh=iAYBNDvloLZQziWxDRSfHjl 7s5M=; b=SSFzOxweqesTF9h9acXqkRYXTzjqgb5Lm6ZYVsiaDEFNNJhqAmhXPRU OKh2qlgBhTDdg6rMjkpZyiEvDFEcwsXbwIXrdTVm5RxvSAR3WidEeNi/aFLN9Ugi LB7BAGrxqYzM4SahWXqXSnQd8EQcb8c7gvL6f8f/ddaVqbvfg7pY= Received: (qmail 79317 invoked by alias); 1 Nov 2016 02:39:32 -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 79295 invoked by uid 89); 1 Nov 2016 02:39:31 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.0 required=5.0 tests=AWL, BAYES_00, FREEMAIL_FROM, RCVD_IN_DNSWL_LOW, RCVD_IN_SORBS_SPAM, SPF_PASS autolearn=ham version=3.3.2 spammy=msebor@redhat.com, worthwhile, mseborredhatcom, H*Ad:D*fu-berlin.de X-HELO: mail-qt0-f196.google.com Received: from mail-qt0-f196.google.com (HELO mail-qt0-f196.google.com) (209.85.216.196) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Tue, 01 Nov 2016 02:39:21 +0000 Received: by mail-qt0-f196.google.com with SMTP id l20so1839616qta.1 for ; Mon, 31 Oct 2016 19:39:20 -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:cc:message-id:date :user-agent:mime-version:in-reply-to; bh=4YZTyMa5g7SXvlI0styXMeLapVjf+zz/thkHd40MyJI=; b=TO+F+woCaqTH61KjoMXpgM9Vq6LDGR6WcQyKEzUDsniaKd7HgWSAjl3HaISqnkR/Jr f0fKDM+YpoJ3eZkDDIaW+gLpuTsxXnDPK7084A1KKU17swR9Tbf/dIovuAfd4BYYwpRb Ip3+oChgkzMWIr4vHnlNkW6VDMgrwZxWWQ8K4+E84SbbMef0kN6M+xkoC+FHr+cvWHCF xYW+vzvIX1ldfH/q3Zzvq03kOjnzFwa7nbQuUmrm8A1u9VH5aFZ/RXXVOgoMAjqudeHX tD4bYkuqkcrjTcXhlpLpeBaLRnXuikl3KQGa4AHG+sYvM7HludY8oHq0h2lAItRVBBsg qW5Q== X-Gm-Message-State: ABUngvf2Rf3/9SFRx9r92/CgtI8owJRDmgRlOy15ZHJc54JUB4BDRpoSdcIcYjJbs0Vlpg== X-Received: by 10.237.38.37 with SMTP id z34mr1947991qtc.73.1477967958998; Mon, 31 Oct 2016 19:39:18 -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 n77sm14817842qkn.28.2016.10.31.19.39.17 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 31 Oct 2016 19:39:18 -0700 (PDT) Subject: Re: [PATCH] enhance buffer overflow warnings (and c/53562) To: gcc-patches@gcc.gnu.org References: <20161031123909.GA9233@physik.fu-berlin.de> <334666bc-6308-aa5f-f63f-40697695152f@gmail.com> From: Martin Sebor Cc: Tobias Burnus Message-ID: <904d9d3b-8662-e714-cc82-e08c72c54c0e@gmail.com> Date: Mon, 31 Oct 2016 20:39:15 -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: <334666bc-6308-aa5f-f63f-40697695152f@gmail.com> X-IsSubscribed: yes Attached is an updated patch that works around the problem with the definition of the NOTE_DATA macro discussed below. I've raised bug 78174 for it and temporarily worked around it in the patch. I'll see if I can come up with a patch to fix the macro the "right way" but would prefer to do that separately. The NOTE_DATA macro is implemented in terms of the RTL_CHECK1 macro that will need to change and that macro is used in many others, so I would rather not mess around with those as part of this patch. I tested the updated patch with today's top of trunk on x86_64. The reason why I missed the problem in the previous patch is because I accidentally tested an earlier version of it. In following up on bug 78174 it occurred to me that the warning option added by the patch for the non-checking functions could perhaps be enhanced to specify the object size checking type (right now it hardcodes type 1 for all non-checking functions). That would make it possible to request looser checking for some functions (e.g., memset, for compatibility with Glibc's setting of zero even at _FORTIFY_SOURCE=2), and stricter checking for others (e.g., strcpy where Glibc uses type-1 with _FORTIFY_SOURCE=2). If this is something people would think would be worthwhile please let me know. Martin On 10/31/2016 10:44 AM, Martin Sebor wrote: > On 10/31/2016 06:39 AM, Tobias Burnus wrote: >> Martin Sebor wrote: >>> Attached is an updated patch that adds checks for excessive sizes >>> and bounds (those in excess of SIZE_MAX / 2), and also enables >>> the same checking for strcat and strncat). This version also >>> fixes an issue with the interpretation of anti-ranges in the >>> first patch. The improvements exposed two bugs in the regression >>> tests. >> >> If I apply this patch to my local trunk - and try to bootstrap GCC, >> bootstrapping fails (on x86-64_gnu-linux) as following. I have not >> tried to figure out whether the warning (-Werror) makes sense or not. >> >> ../../gcc/emit-rtl.c: In function ‘rtx_note* make_note_raw(insn_note)’: >> ../../gcc/emit-rtl.c:3933:59: error: void* memset(void*, int, size_t) >> writing 8 bytes into a region of size 0 overflows the destination >> [-Werror=stringop-overflow] >> memset (&NOTE_DATA (note), 0, sizeof (NOTE_DATA (note))); >> >> >> where NOTE_DATA is defined in rtl.h as >> >> /* Opaque data. */ >> #define NOTE_DATA(INSN) RTL_CHECKC1 (INSN, 3, NOTE) > > Thanks for trying it out! The patch bootstrapped and passed regression > tests for me yesterday, also on x86_64, and today on powepc64le, but > just now I reproduced the error with the top of today's trunk on > x86_64. I think the error is justified because the call to memset > in the make_note_raw function references a fourth element > (rtx_note::rtx_insn::rtx_def::u.fld[3]) of what is just a single > element array. After macro expansion, the memset call itself: > > memset (&((note)->u.fld[3]), 0, sizeof (((note)->u.fld[3]))); > > is within the bounds of the object pointed to by note but the index > 3 is out of bounds for note->u.fld and so undefined. An unpatched > GCC issues a similar error when the call to memset is replaced with > __builtin___memset_chk like so: > > __builtin___memset_chk (&((note)->u.fld[3]), 0, > sizeof (((note)->u.fld[3])), > __builtin_object_size (&((note)->u.fld[3]), 1)); > > /src/gcc/53562/gcc/emit-rtl.c: In function ‘rtx_note* > make_note_raw(insn_note)’: > /src/gcc/53562/gcc/emit-rtl.c:3941:53: warning: call to void* > __builtin___memset_chk(void*, int, long unsigned int, long unsigned int) > will always overflow destination buffer > > The code can be made valid and the warning avoided by deriving > the same address not from an invalid pointer/subscript but rather > from a pointer to the beginning of the union itself and adding > the offset of the fourth element, like so: > > char *p = (char*) &(note)->u.fld[0]; > p += sizeof (((note)->u.fld[0])) * 3; > memset (p, 0, sizeof *p); > > Unfortunately, because the invalid subscript is the result of > the expansion of the RTL_CHECK1() macro fixing this isn't as > straightforward as that. What really should be fixed is the macro > itself. Until then, it can be worked (hacked would be a better > term) around it by storing the result of &NOTE_DATA (note) > expression in a volatile pointer, like so: > > diff --git a/gcc/emit-rtl.c b/gcc/emit-rtl.c > index 8afcfbe..6dd9439 100644 > --- a/gcc/emit-rtl.c > +++ b/gcc/emit-rtl.c > @@ -3930,7 +3930,8 @@ make_note_raw (enum insn_note subtype) > INSN_UID (note) = cur_insn_uid++; > NOTE_KIND (note) = subtype; > BLOCK_FOR_INSN (note) = NULL; > - memset (&NOTE_DATA (note), 0, sizeof (NOTE_DATA (note))); > + void* volatile p = &NOTE_DATA (note); > + memset (p, 0, sizeof (NOTE_DATA (note))); > return note; > } > > 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: 2016-10-30 Martin Sebor PR c/53562 * c.opt (-Wstringop-overflow): New option. gcc/ChangeLog: 2016-10-30 Martin Sebor PR c/53562 PR middle-end/78149 PR middle-end/78138 * builtins.c (expand_builtin_strcat, expand_builtin_strncat): New functions. (get_size_range, check_sizes, check_strncat_sizes): Same. (expand_builtin_memcpy): Call check sizes. (expand_builtin_mempcpy): Same. (expand_builtin_strcpy): Same. (expand_builtin_strncpy): Same. (expand_builtin_memset): Same, (expand_builtin_bzero): Same. (expand_builtin_memory_chk): Same. (maybe_emit_sprintf_chk_warning): Same. (expand_builtin): Handle strcat and strncat. * gcc/emit-rtl.c (make_note_raw): Temporarily work around a bug in the definition of the NOTE_DATA macro. * doc/invoke.texi (Warning Options): Document -Wstringop-overflow. gcc/testsuite/ChangeLog: 2016-10-30 Martin Sebor 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/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. diff --git a/gcc/builtins.c b/gcc/builtins.c index facecd3..02f118c 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); @@ -2967,6 +2969,211 @@ 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. + SLEN is the length of the source sequence (such as in strcpy(d, s), + SLEN = strlen(s)). + 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 (tree exp, tree size, tree maxlen, tree slen, tree objsize) +{ + /* The size of the largest object. (This is way too permissive.) */ + tree maxobjsize = build_int_cst (sizetype, HOST_WIDE_INT_MAX); + + tree range[2] = { NULL_TREE, NULL_TREE }; + + if (!size && !maxlen) + { + /* Handle strlen but not snprintf. */ + size = slen ? slen : maxobjsize; + } + + 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); + + 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_Wstringop_overflow, + "%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_Wstringop_overflow, + "%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 (range[0] == range[1]) + warning_at (loc, OPT_Wstringop_overflow, + (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_Wstringop_overflow, + "%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_Wstringop_overflow, + "%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_Wstringop_overflow, + "%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_Wstringop_overflow, + "%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_Wstringop_overflow, + "%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; +} + /* 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 @@ -2983,6 +3190,14 @@ expand_builtin_memcpy (tree exp, rtx target) tree dest = CALL_EXPR_ARG (exp, 0); tree src = CALL_EXPR_ARG (exp, 1); tree len = CALL_EXPR_ARG (exp, 2); + + tree objsize = NULL_TREE; + unsigned HOST_WIDE_INT uhwisize; + if (compute_builtin_object_size (dest, 1, &uhwisize)) + objsize = build_int_cst (sizetype, uhwisize); + + check_sizes (exp, len, /*maxlen=*/NULL_TREE, NULL_TREE, objsize); + return expand_builtin_memcpy_args (dest, src, len, target, exp); } } @@ -3037,10 +3252,21 @@ expand_builtin_mempcpy (tree exp, rtx target, machine_mode mode) 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 destsize = NULL_TREE; + unsigned HOST_WIDE_INT objsize; + if (compute_builtin_object_size (dest, 1, &objsize)) + destsize = build_int_cst (sizetype, objsize); + + /* 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_sizes (exp, len, NULL_TREE, NULL_TREE, destsize)) + return expand_builtin_mempcpy_args (dest, src, len, + target, mode, /*endp=*/ 1, + exp); } + return NULL_RTX; } /* Expand an instrumented call EXP to the mempcpy builtin. @@ -3212,6 +3438,50 @@ 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)) + 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. */ + + /* 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. At a (future) stricter warning setting the longest length + should be used instead. */ + if (lenrange[0]) + { + /* Try to determine the size of the destination object into + which the source is being copied. */ + tree destsize = NULL_TREE; + unsigned HOST_WIDE_INT objsize; + if (compute_builtin_object_size (dest, 1, &objsize)) + destsize = build_int_cst (sizetype, objsize); + + /* Add one for the terminating nul. */ + tree len = fold_build2 (PLUS_EXPR, size_type_node, lenrange[0], + size_one_node); + check_sizes (exp, NULL_TREE, NULL_TREE, len, 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 @@ -3224,6 +3494,30 @@ expand_builtin_strcpy (tree exp, rtx target) { tree dest = CALL_EXPR_ARG (exp, 0); tree src = CALL_EXPR_ARG (exp, 1); + + /* 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. At a (future) stricter warning setting the longest length + should be used instead. */ + if (lenrange[0]) + { + /* Try to determine the size of the destination object into + which the source is being copied. */ + tree destsize = NULL_TREE; + unsigned HOST_WIDE_INT objsize; + if (compute_builtin_object_size (dest, 1, &objsize)) + destsize = build_int_cst (sizetype, objsize); + + /* Add one for the terminating nul. */ + tree len = fold_build2 (PLUS_EXPR, size_type_node, lenrange[0], + size_one_node); + check_sizes (exp, NULL_TREE, NULL_TREE, len, destsize); + } + return expand_builtin_strcpy_args (dest, src, target); } return NULL_RTX; @@ -3334,6 +3628,130 @@ 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) + { + /* 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. */ + unsigned HOST_WIDE_INT destsize; + if (compute_builtin_object_size (dest, 1, &destsize)) + objsize = build_int_cst (sizetype, destsize); + } + + /* 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 || 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 (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)) + 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. */ + + /* Try to determine the size of the destination object into which + the source is being copied. */ + tree destsize = NULL_TREE; + unsigned HOST_WIDE_INT objsize; + if (compute_builtin_object_size (dest, 1, &objsize)) + destsize = build_int_cst (sizetype, objsize); + + /* 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 || 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 (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. */ @@ -3347,9 +3765,39 @@ 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); + /* 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. At a (future) stricter warning setting the longest length + should be used instead. */ + + /* Try to determine the size of the destination object into which + the source is being copied. */ + tree destsize = NULL_TREE; + unsigned HOST_WIDE_INT objsize; + if (compute_builtin_object_size (dest, 1, &objsize)) + destsize = build_int_cst (sizetype, objsize); + + /* Add one for the terminating nul. */ + tree srclen = (lenrange[0] + ? fold_build2 (PLUS_EXPR, size_type_node, lenrange[0], + size_one_node) + : NULL_TREE); + /* The number of bytes to write is LEN but check_sizes will also + check SRCLEN if LEN's value isn't known. */ + check_sizes (exp, len, /*maxlen=*/NULL_TREE, srclen, 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; @@ -3442,6 +3890,15 @@ expand_builtin_memset (tree exp, rtx target, machine_mode mode) tree dest = CALL_EXPR_ARG (exp, 0); tree val = CALL_EXPR_ARG (exp, 1); tree len = CALL_EXPR_ARG (exp, 2); + + /* Try to determine the size of the destination object. */ + tree objsize = NULL_TREE; + unsigned HOST_WIDE_INT uhwisize; + if (compute_builtin_object_size (dest, 1, &uhwisize)) + objsize = build_int_cst (sizetype, uhwisize); + + check_sizes (exp, len, NULL_TREE, NULL_TREE, objsize); + return expand_builtin_memset_args (dest, val, len, target, mode, exp); } } @@ -3633,6 +4090,14 @@ expand_builtin_bzero (tree exp) dest = CALL_EXPR_ARG (exp, 0); size = CALL_EXPR_ARG (exp, 1); + /* Try to determine the size of the destination object. */ + tree objsize = NULL_TREE; + unsigned HOST_WIDE_INT uhwisize; + if (compute_builtin_object_size (dest, 1, &uhwisize)) + objsize = build_int_cst (sizetype, uhwisize); + + check_sizes (exp, size, NULL_TREE, NULL_TREE, objsize); + /* 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 @@ -6119,12 +6584,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) @@ -9212,22 +9689,20 @@ 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 (exp, len, NULL_TREE, 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) @@ -9313,68 +9788,84 @@ 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 length of the source sequence of the memory operation, and + the size of the destination object. */ + tree srclen = NULL_TREE; + tree objsize = NULL_TREE; + /* The length of the sequence that the source sequence is being + concatenated with (as with __strcat_chk) or null if it isn't. */ + tree catlen = 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. */ + srclen = 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. */ + catlen = CALL_EXPR_ARG (exp, 0); + srclen = CALL_EXPR_ARG (exp, 1); + objsize = CALL_EXPR_ARG (exp, 2); break; + case BUILT_IN_STRNCAT_CHK: + catlen = CALL_EXPR_ARG (exp, 0); + srclen = 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); + srclen = 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; + /* Compute the length of shortest string the source pointer points to. */ + if (srclen) + { + tree lenrange[2]; + get_range_strlen (srclen, lenrange); + srclen = lenrange[0]; + } - if (is_strlen) + if (catlen && 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) + + if (srclen) { - 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; + srclen = fold_build2 (PLUS_EXPR, TREE_TYPE (srclen), srclen, + size_one_node); + if (maxlen && tree_fits_uhwi_p (maxlen) + && tree_int_cst_lt (maxlen, srclen)) + srclen = maxlen; } - 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 (exp, NULL_TREE, maxlen, srclen, objsize); } /* Emit warning if a buffer overflow is detected at compile time @@ -9428,10 +9919,9 @@ 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 (exp, NULL_TREE, 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 e146781..6635464 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -647,6 +647,10 @@ 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++ Var(warn_stringop_overflow) Init(1) Warning +Warn about buffer overflow in string manipulation functions. + 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 d9667e7..81cbe63 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -299,6 +299,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 @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 @@ -4831,6 +4832,27 @@ comparisons, so this warning level gives a very large number of false positives. @end table +@item -Wstringop-overflow +@opindex Wstringop-overflow +@opindex Wno-stringop-overflow +Warn for calls to string manipulation functions such as memcpy and strcpy +hat are determined to overflow the destination buffer. The option works +best with optimization enabled but it can detect a small subset of buffer +overflows even without optimization. In any case, the option warns about +just a subset of buffer overflows detected by the corresponding overflow +checking built-ins. @xref{Object Size Checking}. For example, +@option{-Wstringop-overflow} will warn on the following: + +@smallexample +char buf[6]; +void f (int i) +@{ + strcpy (buf, i < 0 ? "yellow" : "orange"); +@}; +@end smallexample + +The @option{-Wstringop-overflow} option is enabled by default. + @item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @opindex Wsuggest-attribute= @opindex Wno-suggest-attribute= diff --git a/gcc/emit-rtl.c b/gcc/emit-rtl.c index 2d6d1eb..9de68d0 100644 --- a/gcc/emit-rtl.c +++ b/gcc/emit-rtl.c @@ -3929,7 +3929,14 @@ make_note_raw (enum insn_note subtype) INSN_UID (note) = cur_insn_uid++; NOTE_KIND (note) = subtype; BLOCK_FOR_INSN (note) = NULL; - memset (&NOTE_DATA (note), 0, sizeof (NOTE_DATA (note))); + + /* FIXME: The NOTE_DATA() macro uses an invalid array subscript + that causes a -Wstringop-overflow warning in the call to memset + below. The resulting pointer is valid but the way it's derived + is undefined. Hide the pointer computation from memset to avoid + the warning until the macro is fixed. */ + void* volatile p = &NOTE_DATA (note); + memset (p, 0, sizeof (NOTE_DATA (note))); return note; } 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..85b3977 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 "overflows" } } void bar (const char *, int); 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/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-3.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-3.c new file mode 100644 index 0000000..3b0ba0f --- /dev/null +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-3.c @@ -0,0 +1,344 @@ +/* Test exercising buffer overflow warnings emitted for __*_chk builtins + in cases where the destination involves a non-constant offset into + an object of known size. */ +/* { 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; + +void sink (void*); + +/* Define memcpy as a macro (as opposed to an inline function) so that + warnings point to its invocation in the tests (as opposed to its + definition), making sure its first argument is evaluated exactly + once. */ +#define memcpy(d, s, n) \ + do { \ + __typeof__ (d) __d = (d); \ + __builtin___memcpy_chk (__d, s, n, __builtin_object_size (__d, 1)); \ + sink (__d); \ + } while (0) + +/* Function to generate a unique offset 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. */ +ptrdiff_t random_value (void); + +/* For brevity. */ +#define X() random_value () + +/* Test memcpy with a variable offset not known to be in any range + and with a constant number of bytes. */ + +void test_var_const (const void *p) +{ + char buf[5]; + + memcpy (buf + X(), p, 6); /* { dg-warning "writing 6 bytes into a region of size 5" } */ + + memcpy (&buf[X()], p, 6); /* { dg-warning "writing" } */ + + /* Since X() below can be assumed to be non-negative (otherwise it would + result in forming a pointer before the beginning of BUF), then because + of the +1 added to the resulting address there must be at most enough + room for (sizeof(buf) - 1) or 4 bytes. */ + memcpy (&buf[X()] + 1, p, 4); + + memcpy (&buf[X()] + 1, p, 5); /* { dg-warning "writing" } */ + + memcpy (&buf[X()] + 5, p, 6); /* { dg-warning "writing" } */ + + /* The negative constant offset below must have no effect on the maximum + size of the buffer. */ + memcpy (&buf[X()] - 1, p, 5); + + memcpy (&buf[X()] - 1, p, 6); /* { dg-warning "writing" } */ + + memcpy (&buf[X()] - 5, p, 6); /* { dg-warning "writing" } */ + + memcpy (&buf[X()] + X(), p, 5); + + memcpy (&buf[X()] + X(), p, 6); /* { dg-warning "writing" } */ + + memcpy (&buf[X()] - X(), p, 5); + + memcpy (&buf[X()] - X(), p, 6); /* { dg-warning "writing" } */ +} + +static inline size_t +range (size_t min, size_t max) +{ + const size_t val = random_value (); + return val < min || max < val ? min : val; +} + +/* Test memcpy with a variable offset not known to be in any range + and with a number of bytes bounded by a known range. */ + +void test_var_range (void *dst, const void *p) +{ + char buf[5]; + + memcpy (&buf[X()], p, range (0, 5)); + memcpy (&buf[X()], p, range (1, 5)); + memcpy (&buf[X()], p, range (2, 5)); + memcpy (&buf[X()], p, range (3, 5)); + memcpy (&buf[X()], p, range (4, 5)); + + memcpy (&buf[X()], p, range (6, 7)); /* { dg-warning "writing" } */ + + const size_t max = SIZE_MAX; + memcpy (&buf[X()], p, range (max - 1, max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ + + memcpy (dst, p, range (max / 2 + 1, max)); /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */ +} + +/* Return an ingeger in the range [MIN, MASK]. Use bitwise operations + rather than inequality to avoid relying on optimization passes + beyond Early Value Range Propagation that __builtin_object_size + doesn't make use of (yet). */ + +static inline size_t +simple_range (unsigned min, unsigned mask) +{ + return ((unsigned)random_value () & mask) | (min & mask); +} + +/* For brevity. */ +#define R(min, max) simple_range (min, max) + +void test_range_auto (const void *p) +{ + char buf[5]; + + memcpy (buf + R (0, 1), p, 6); /* { dg-warning "writing" } */ + + /* Some of these could be diagnosed as an extension because they would + overflow when (if) the non-const index were sufficiently large. + The challenge is distinguishing a range where a variable is likely + to exceed the minimum required for the overflow to occur from one + where it isn't so likely. */ + memcpy (buf + R (0, 1), p, 5); + + memcpy (buf + R (0, 1), p, 4); + + memcpy (buf + R (0, 2), p, 3); + + memcpy (buf + R (0, 3), p, 2); + + memcpy (buf + R (0, 7), p, 1); + + memcpy (buf + R (0, 5), p, 0); + + /* The offset is known to be at least 1 so the size of the object + is at most 4. */ + memcpy (buf + R (1, 2), p, 3); + + memcpy (buf + R (1, 3), p, 3); + + memcpy (buf + R (1, 2), p, 4); + + memcpy (buf + R (1, 3) + 1, p, 4); /* { dg-warning "writing" } */ + + memcpy (buf + R (1, 3), p, 5); /* { dg-warning "writing" } */ + + memcpy (buf + R (2, 3), p, 2); + + memcpy (buf + R (2, 3), p, 3); + + memcpy (buf + R (2, 3) + 1, p, 3); /* { dg-warning "writing" } */ + + memcpy (buf + R (2, 3), p, 4); /* { dg-warning "writing" } */ + + memcpy (buf + R (3, 4), p, 2); + + memcpy (buf + R (3, 7), p, 3); /* { dg-warning "writing" } */ + + memcpy (buf + R (3, 7), p, 9); /* { dg-warning "writing" } */ + + memcpy (&buf[R (0, 1)] + 1, p, 4); + + memcpy (&buf[R (0, 2)] + 1, p, 4); + + memcpy (&buf[R (0, 3)] + 1, p, 4); + + memcpy (&buf[R (0, 4)] + 1, p, 4); + + memcpy (&buf[R (0, 5)] + 1, p, 4); + + memcpy (&buf[R (0, 2)] + 2, p, 4); /* { dg-warning "writing" } */ + + memcpy (&buf[R (0, 2)] - 2, p, 3); + memcpy (&buf[R (0, 2)] - 2, p, 3); + + memcpy (&buf[R (0, 2)] - 2, p, 3); + + memcpy (&buf[R (5, 15)], p, 1); /* { dg-warning "writing" } */ + + /* With the offset given by the two ranges below there is at most + 1 byte left. */ + memcpy (buf + R (1, 2) + R (3, 4), p, 1); + + memcpy (buf + R (1, 3) + R (3, 7), p, 2); /* { dg-warning "writing" } */ + + /* Unfortunately, the following isn't handled quite right: only + the lower bound given by the first range is used, the second + one is disregarded. */ + memcpy (&buf [R (1, 2)] + R (3, 4), p, 2); /* { dg-warning "writing" "index in range plus offset in range" { xfail *-*-* } } */ + + memcpy (buf + R (2, 3) + R (2, 3), p, 1); + + memcpy (buf + R (2, 3) + R (2, 3), p, 2); /* { dg-warning "writing" } */ +} + +void test_range_malloc (const void *p) +{ + char *buf = __builtin_malloc (5); + + memcpy (buf + R (0, 1), p, 6); /* { dg-warning "writing" } */ + + memcpy (buf + R (0, 1), p, 5); + + memcpy (buf + R (0, 1), p, 4); + + memcpy (buf + R (0, 2), p, 3); + + memcpy (buf + R (0, 3), p, 2); + + memcpy (buf + R (0, 4), p, 1); + + memcpy (buf + R (0, 5), p, 0); +} + +void test_range_schar (signed char i, const void *s) +{ + char a [130]; + + /* The range of I is [-128, 127] so the size of the destination below + is at most 2 (i.e., 130 - 128) bytes. */ + memcpy (&a [130] + i, s, 2); + + /* Strictly, the range of I below is [0, 127] because a negative value + would result in forming an invalid pointer, so the destination is at + most 2 bytes. */ + memcpy (&a [i] + 128, s, 2); + + /* Reset I's range just in case it gets set above. */ + i = random_value (); + memcpy (&a [130] + i, s, 3); /* { dg-warning "writing" } */ + + i = random_value (); + memcpy (&a [i] + 128, s, 3); /* { dg-warning "writing" } */ +} + +void test_range_uchar (unsigned char i, const void *s) +{ + char a [260]; + + /* The range of I is [0, 255] so the size of the destination below + is at most 2 (i.e., 260 - 258 + 0) bytes. */ + memcpy (&a [258] + i, s, 2); + + memcpy (&a [i] + 128, s, 2); + + /* Reset I's range just in case it gets set above. */ + i = random_value (); + memcpy (&a [258] + i, s, 3); /* { dg-warning "writing" } */ + + i = random_value (); + memcpy (&a [i] + 258, s, 3); /* { dg-warning "writing" } */ + + i = random_value (); + memcpy (&a [259] + i, s, 2); /* { dg-warning "writing" } */ + + i = random_value (); + memcpy (&a [i] + 259, s, 2); /* { dg-warning "writing" } */ + + i = random_value (); + memcpy (&a [260] + i, s, 1); /* { dg-warning "writing" } */ + + i = random_value (); + memcpy (&a [i] + 260, s, 1); /* { dg-warning "writing" } */ + + i = random_value (); + memcpy (&a [260] + i, s, 0); + + i = random_value (); + memcpy (&a [i] + 260, s, 0); +} + +void test_range_int (int i, const void *s) +{ + const size_t max = (size_t)INT_MAX * 2 + 1; + + char *a = __builtin_malloc (max); + + memcpy (&a [max] + i, s, INT_MAX); + memcpy (&a [max] - i, s, INT_MAX); + /* &*/ + memcpy (&a [max] + i, s, INT_MAX + (size_t)1); + memcpy (&a [max] - i, s, INT_MAX + (size_t)1); /* { dg-warning "writing" } */ + memcpy (&a [max] + i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */ + memcpy (&a [max] - i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */ + + char *end = &a [max]; + memcpy (end + i, s, INT_MAX); + memcpy (end - i, s, INT_MAX); + /* &*/ + memcpy (end + i, s, INT_MAX + (size_t)1); + memcpy (end - i, s, INT_MAX + (size_t)1); /* { dg-warning "writing" } */ + memcpy (end + i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */ + memcpy (end - i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */ + + memcpy (&a [i] + max, s, 1); +} + + +void test_range_ptrdiff_t (ptrdiff_t i, const void *s) +{ + const size_t max = PTRDIFF_MAX; + + char *a = __builtin_malloc (max); + + memcpy (&a [max] + i, s, max); + + memcpy (&a [i] + max - 1, s, 1); +} + +void test_range_size_t (size_t i, const void *s) +{ + const size_t diffmax = PTRDIFF_MAX; + + char *a = __builtin_malloc (diffmax); + + memcpy (&a [diffmax] + i, s, 0); + memcpy (&a [i] + diffmax, s, 0); + + memcpy (&a [diffmax] + i, s, 1); /* { dg-warning "writing" } */ + + memcpy (&a [i] + diffmax, s, 1); /* { dg-warning "writing" } */ +} + +struct S { + int i; + char a7[7]; + int b; +}; + +void test_range_member_array (struct S *s, const void *p) +{ + memcpy (s->a7 + R (0, 1), p, 6); + + memcpy (s->a7 + R (0, 1), p, 7); + + memcpy (s->a7 + R (0, 1), p, 8); /* { dg-warning "writing" } */ + + memcpy (&s->a7 [R (0, 1)], p, 7); + + memcpy (&s->a7 [R (1, 3)], p, 7); /* { dg-warning "writing" } */ +} 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..7979a13 --- /dev/null +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c @@ -0,0 +1,459 @@ +/* Test exercising buffer overflow warnings emitted for __*_chk builtins + in cases where the destination involves a non-constant offset into + an object of known size. */ +/* { 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, 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 *-*-* } } */ + } +} + +/* 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 " } */ +} + +/* 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 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 *d, const char *s) +{ + 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" } */ + + /* Exercise strncpy into a destination of unknown size with a valid + and invalid constant number of bytes. */ + strncpy (d, S (0), UR (5, 6)); + strncpy (d, S (1), UR (6, 7)); + strncpy (d, S (2), UR (7, 8)); + + strncpy (d, S (3), UR (ssize_max, ssize_max + 1)); + + strncpy (d, 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-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/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 {