Bug 84732 - false-positive -Wstringop-truncation warning with -fsanitize-coverage=trace-pc
Summary: false-positive -Wstringop-truncation warning with -fsanitize-coverage=trace-pc
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: sanitizer (show other bugs)
Version: 8.0.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic
Depends on:
Blocks: Wstringop-truncation
  Show dependency treegraph
 
Reported: 2018-03-06 12:19 UTC by Arnd Bergmann
Modified: 2021-05-04 12:23 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2018-03-06 00:00:00


Attachments
linux/drivers/staging/lustre/lnet/lnet/lib-socket.c, preprocessed, not reduced (370.53 KB, application/gzip)
2018-03-06 12:19 UTC, Arnd Bergmann
Details
drivers/gpu/drm/drm_property.c, preprocessed (253.50 KB, application/gzip)
2018-03-07 13:12 UTC, Arnd Bergmann
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Arnd Bergmann 2018-03-06 12:19:34 UTC
Created attachment 43576 [details]
linux/drivers/staging/lustre/lnet/lnet/lib-socket.c, preprocessed, not reduced

I ran into this warning for what looks like correct code in the linux kernel that we should not warn about:

$ aarch64-linux-gcc-8.0.1 -fno-strict-aliasing -Wno-pointer-sign -fsanitize-coverage=trace-pc  -Wall -O2 -c lib-socket.i
In file included from /git/arm-soc/arch/arm64/include/asm/processor.h:37,
                 from /git/arm-soc/arch/arm64/include/asm/spinlock.h:21,
                 from /git/arm-soc/include/linux/spinlock.h:88,
                 from /git/arm-soc/include/linux/wait.h:9,
                 from /git/arm-soc/include/linux/net.h:23,
                 from /git/arm-soc/drivers/staging/lustre/lnet/lnet/lib-socket.c:37:
/git/arm-soc/drivers/staging/lustre/lnet/lnet/lib-socket.c: In function 'lnet_ipif_query':
/git/arm-soc/include/linux/string.h:254:9: warning: '__builtin_strncpy' specified bound 16 equals destination size [-Wstringop-truncation]
  return __builtin_strncpy(p, q, size);
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/git/arm-soc/include/linux/string.h:254:9: warning: '__builtin_strncpy' specified bound 16 equals destination size [-Wstringop-truncation]
  return __builtin_strncpy(p, q, size);
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/git/arm-soc/include/linux/string.h:254:9: warning: '__builtin_strncpy' specified bound 16 equals destination size [-Wstringop-truncation]
  return __builtin_strncpy(p, q, size);
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

See https://elixir.bootlin.com/linux/v4.15/source/drivers/staging/lustre/lnet/lnet/lib-socket.c#L99 for the original source code. Without -fsanitize-coverage=trace-pc, the strlen() comparison is sufficient to avoid that warning, with fsanitize=coverage=trace-pc, that logic fails:

	if (strlen(name) > sizeof(ifr.ifr_name) - 1)
		return -E2BIG;
	strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));


I can probably create a reduced test case if that helps.
Comment 1 Martin Liška 2018-03-06 12:25:06 UTC
Let me take a look.
Comment 2 Martin Liška 2018-03-06 14:44:49 UTC
Reduced test-case:

$ cat ~/Programming/testcases/ice.i
char *arg;

struct buffer{
  char a[16];
};

struct buffer a, b;

void e(void) {
  if (__builtin_strlen(arg) > 1)
    __builtin_strncpy(a.a, arg, sizeof(struct buffer));
}

Without coverage sanitization:

$ ./xgcc -B. ~/Programming/testcases/ice.i -c -O2 -Wall -fdump-tree-strlen=/dev/stdout

;; Function e (e, funcdef_no=0, decl_uid=1962, cgraph_uid=0, symbol_order=3)

e ()
{
  char * arg.0_1;
  long unsigned int _2;

  <bb 2> [local count: 1073741825]:
  arg.0_1 = arg;
  _2 = __builtin_strlen (arg.0_1);
  if (_2 > 1)
    goto <bb 3>; [41.48%]
  else
    goto <bb 4>; [58.52%]

  <bb 3> [local count: 445388109]:
  __builtin_strncpy (&a.a, arg.0_1, 16);

  <bb 4> [local count: 1073741825]:
  return;

}

With:

./xgcc -B. ~/Programming/testcases/ice.i -c -O2 -fsanitize-coverage=trace-pc -fdump-tree-strlen=/dev/stdout

;; Function e (e, funcdef_no=0, decl_uid=2322, cgraph_uid=0, symbol_order=3)

e ()
{
  char * arg.0_1;
  long unsigned int _2;

  <bb 2> [local count: 1073741825]:
  __builtin___sanitizer_cov_trace_pc ();
  arg.0_1 = arg;
  _2 = __builtin_strlen (arg.0_1);
  if (_2 > 1)
    goto <bb 3>; [41.48%]
  else
    goto <bb 4>; [58.52%]

  <bb 3> [local count: 445388109]:
  __builtin___sanitizer_cov_trace_pc ();
  __builtin_strncpy (&a.a, arg.0_1, 16);

  <bb 4> [local count: 1073741825]:
  __builtin___sanitizer_cov_trace_pc ();
  return;

}

I guess it somehow confuses VRP, Martin can you please take a look? Note that __builtin___sanitizer_cov_trace_pc is pure function, can't modify memory in original program.
Comment 3 Martin Sebor 2018-03-06 16:42:51 UTC
In general, by inserting their instrumentation the sanitizers cause all sorts of trouble for middle-end warnings.  The usual answer has been to disable -Werror when using -fsanitize.  Personally, I think that's less than optimal (ideally we want both good static analysis and robust runtime detection), but sometimes it may be the only viable solution.

The -Wstringop-truncation suppression logic looks for the next statement after a strncpy call that writes a NUL into the destination (actually, it looks for an assignment of any value to the destination for now, but that will likely change in the future).  The __builtin___sanitizer_cov_trace_pc() call inserted by the sanitizer just after the strncpy() call disables the suppression.

  __builtin_strncpy (&a.a, arg.0_1, 16);

  <bb 4> [local count: 1073741825]:
  __builtin___sanitizer_cov_trace_pc ();

There are other similar examples where this can happen (e.g., bug 84624).  It might be possible to deal with some of them either by hardcoding a whitelist of things to skip over, or by making the logic smart enough to see that some calls/expressions cannot access (read from) the strncpy destination, or some combination of the two.  Richi has been suggesting the latter approach.  The challenge (for me) is to figure out how to avoid false negatives on things like:

  void f (const char *s)
  {
    char d[8];

    char *p = strncpy (d, s, sizeof d);   // want a -Wstringop-truncation here

    foo (p);   // reads a string from d

    d[sizeof d - 1] = '\0';
  }

or even:

  extern char *p;

  void f (const char *s)
  {
    char d[8];

    p = strncpy (d, s, sizeof d);   // also want a -Wstringop-truncation here

    foo ();   // also reads a string from d through p

    d[sizeof d - 1] = '\0';
  }
Comment 4 Jakub Jelinek 2018-03-06 16:57:01 UTC
(In reply to Martin Liška from comment #2)
> I guess it somehow confuses VRP, Martin can you please take a look? Note
> that __builtin___sanitizer_cov_trace_pc is pure function, can't modify
> memory in original program.

No, __builtin___sanitizer_cov_trace_pc is certainly not pure and can't really be, otherwise would optimize them all away (nothing uses their void return value),
so the problem is that it invalidates all recorded string lengths, at least those that escape (but in this testcase it is a global variable).
As __sanitizer_cov_trace_pc is a user-supplied function, I don't really think we can assume anything on it (e.g. that it will not change any global or escaped local variables, it can change them and most likely will do at least some of them, otherwise it would be useless).
Comment 5 Arnd Bergmann 2018-03-07 13:12:41 UTC
Created attachment 43586 [details]
drivers/gpu/drm/drm_property.c, preprocessed

I found another case that appears to be related but not the same, attaching another (non-reduced) file for reference. The code that triggered a warning this time is:

        strncpy(property->name, name, DRM_PROP_NAME_LEN);
        property->name[DRM_PROP_NAME_LEN-1] = '\0';

but unlike the first one, this only happens with -fsanitize=kernel-address but not with -fsanitize-coverage=trace-pc:

$ x86_64-linux-gcc-8.0.1 -fno-strict-aliasing -O2 -Wall -S drm_property.i   -fsanitize=kernel-address

/git/arm-soc/drivers/gpu/drm/drm_property.c: In function 'drm_property_create':
/git/arm-soc/include/linux/string.h:254:9: warning: '__builtin_strncpy' specified bound 32 equals destination size [-Wstringop-truncation]
  return __builtin_strncpy(p, q, size);
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/git/arm-soc/drivers/gpu/drm/drm_property.c: In function 'drm_property_add_enum':
/git/arm-soc/include/linux/string.h:254:9: warning: '__builtin_strncpy' specified bound 32 equals destination size [-Wstringop-truncation]
  return __builtin_strncpy(p, q, size);
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/git/arm-soc/include/linux/string.h:254:9: warning: '__builtin_strncpy' specified bound 32 equals destination size [-Wstringop-truncation]
  return __builtin_strncpy(p, q, size);
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Comment 6 Jakub Jelinek 2018-03-07 13:41:47 UTC
Short testcase for the latter, -O2 -Wall vs. -O2 -Wall -fsanitize=address:
struct S { char p[32]; };

struct S *
foo (char *q)
{
  struct S *p = __builtin_malloc (sizeof (struct S));
  __builtin_strncpy (p->p, q, 32);
  p->p[31] = '\0';
  return p;
}

With -fsanitize={,kernel-}address there is:
  __builtin_strncpy (_1, q_5(D), 32);
  _8 = &p_4->p[31];
  ASAN_CHECK (7, _8, 1, 1);
  p_4->p[31] = 0;
So, the -Wstringop-truncation would need to do the suggested alias search for the store rather than next statement it does now, and in addition to that would need to ignore ASAN_CHECK, because those are guaranteed not to actually read the passed memory, just to check corresponding shadow memory.
Comment 7 Martin Liška 2018-03-08 08:38:18 UTC
> So, the -Wstringop-truncation would need to do the suggested alias search
> for the store rather than next statement it does now, and in addition to
> that would need to ignore ASAN_CHECK, because those are guaranteed not to
> actually read the passed memory, just to check corresponding shadow memory.

Which is probably similar to what was seen in PR84307, am I right?
Comment 8 Martin Liška 2018-03-08 08:38:55 UTC
(In reply to Jakub Jelinek from comment #4)
> (In reply to Martin Liška from comment #2)
> > I guess it somehow confuses VRP, Martin can you please take a look? Note
> > that __builtin___sanitizer_cov_trace_pc is pure function, can't modify
> > memory in original program.
> 
> No, __builtin___sanitizer_cov_trace_pc is certainly not pure and can't
> really be, otherwise would optimize them all away (nothing uses their void
> return value),
> so the problem is that it invalidates all recorded string lengths, at least
> those that escape (but in this testcase it is a global variable).
> As __sanitizer_cov_trace_pc is a user-supplied function, I don't really
> think we can assume anything on it (e.g. that it will not change any global
> or escaped local variables, it can change them and most likely will do at
> least some of them, otherwise it would be useless).

I see, I was probably too eager :)
Comment 9 Arnd Bergmann 2018-04-09 14:36:16 UTC
One more instance got added to the kernel today:

In file included from /git/arm-soc/include/trace/perf.h:90,
                 from /git/arm-soc/include/trace/define_trace.h:97,
                 from /git/arm-soc/include/trace/events/fscache.h:537,
                 from /git/arm-soc/fs/fscache/internal.h:32,
                 from /git/arm-soc/fs/fscache/main.c:20:
/git/arm-soc/include/trace/events/fscache.h: In function 'perf_trace_fscache_netfs':
/git/arm-soc/include/trace/events/fscache.h:200:1261: warning: 'strncpy' specified bound 8 equals destination size [-Wstringop-truncation]
 TRACE_EVENT(fscache_netfs,

Same as the one from comment #5, this one happen for -fsanitize=kernel-address and is based on simple code that we don't warn for without sanitizer:

       strncpy(__entry->name, netfs->name, 8);
       __entry->name[7]            = 0;

I can probably work around that by turning off -Wstringop-truncation whenever sanitizers enabled in the kernel configuration (we already do that for -Wmaybe-uninitialized), if this one is unlikely to get fixed before the gcc-8 release.