diff mbox series

Add -fsanitize=pointer-{compare,subtract}.

Message ID 6113595f-93a0-7400-0800-496ceadbfb2a@suse.cz
State New
Headers show
Series Add -fsanitize=pointer-{compare,subtract}. | expand

Commit Message

Martin Liška Oct. 6, 2017, 12:46 p.m. UTC
Hi.

Adding a missing functionality mentioned and explained here:
https://github.com/google/sanitizers/wiki/AddressSanitizerClangVsGCC-(5.0-vs-7.1)#feature-8

Currently it only works for heap allocated variables. I'm working on support for stack and global
variables.

The functionality is not included in -fsanitize=address by default, one needs to explicitly ask
for both instrumentation and enabling in run-time. It's expensive check.

Ready to be installed?
Martin

gcc/ChangeLog:

2017-10-06  Martin Liska  <mliska@suse.cz>

	* asan.c (is_pointer_compare_opcode): New function.
	(instrument_pointer_comparison): Likewise.
	(asan_instrument): Handle SANITIZE_POINTER_COMPARE and
	SANITIZE_POINTER_SUBTRACT.
	(gate_asan): Likewise.
	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* gcc.c (SANITIZER_EARLY_SPEC): Handle
	-fsanitize=pointer-compare and -fsanitize=pointer-subtract.
	(SANITIZER_SPEC): Likewise.
	(sanitize_spec_function): Likewise.
	* opts.c (finish_options): Add checking for the options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE): New builtin.
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.
	* toplev.c (process_options):  Handle SANITIZE_POINTER_COMPARE and
	SANITIZE_POINTER_SUBTRACT.

gcc/testsuite/ChangeLog:

2017-10-06  Martin Liska  <mliska@suse.cz>

	* gcc.dg/asan/pointer-compare-1.c: New test.
	* gcc.dg/asan/pointer-compare-2.c: New test.
	* gcc.dg/asan/pointer-subtract-1.c: New test.
	* gcc.dg/asan/pointer-subtract-2.c: New test.
---
 gcc/asan.c                                     | 104 ++++++++++++++++++++++++-
 gcc/doc/invoke.texi                            |  20 +++++
 gcc/flag-types.h                               |   2 +
 gcc/gcc.c                                      |  12 ++-
 gcc/opts.c                                     |  10 +++
 gcc/sanitizer.def                              |   4 +
 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c  |  31 ++++++++
 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c  |  19 +++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c |  31 ++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c |  19 +++++
 gcc/toplev.c                                   |   4 +-
 11 files changed, 253 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c

Comments

Jakub Jelinek Oct. 6, 2017, 1:33 p.m. UTC | #1
On Fri, Oct 06, 2017 at 02:46:05PM +0200, Martin Liška wrote:
> +	  if (sanitize_comparison_p)
> +	    {
> +	      if (is_gimple_assign (s)
> +		  && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS
> +		  && POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (s)))
> +		  && POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs2 (s)))
> +		  && is_pointer_compare_opcode (gimple_assign_rhs_code (s)))

Isn't it better to test is_pointer_compare_opcode right after
is_gimple_assign and leave the gimple_assign_rhs_class test out?

> +		{
> +		  ptr1 = gimple_assign_rhs1 (s);
> +		  ptr2 = gimple_assign_rhs2 (s);
> +		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
> +		}
> +	      else if (gimple_code (s) == GIMPLE_COND
> +		       && POINTER_TYPE_P (TREE_TYPE (gimple_cond_lhs (s)))
> +		       && POINTER_TYPE_P (TREE_TYPE (gimple_cond_rhs (s)))
> +		       && is_pointer_compare_opcode (gimple_cond_code (s)))
> +		{
> +		  ptr1 = gimple_cond_lhs (s);
> +		  ptr2 = gimple_cond_rhs (s);
> +		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
> +		}

You don't handle the case when there is a COND_EXPR with pointer comparison
in the condition.

> +	    }
> +
> +	  if (sanitize_subtraction_p
> +	      && is_gimple_assign (s)
> +	      && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS

The above isn't really needed.

> +	      && gimple_assign_rhs_code (s) == MINUS_EXPR)
> +	    {
> +	      tree rhs1 = gimple_assign_rhs1 (s);
> +	      tree rhs2 = gimple_assign_rhs2 (s);
> +
> +	      if (TREE_CODE (rhs1) == SSA_NAME
> +		  || TREE_CODE (rhs2) == SSA_NAME)
> +		{
> +		  gassign *def1
> +		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs1));
> +		  gassign *def2
> +		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs2));
> +
> +		  if (def1 && def2
> +		      && gimple_assign_rhs_class (def1) == GIMPLE_UNARY_RHS
> +		      && gimple_assign_rhs_class (def2) == GIMPLE_UNARY_RHS)
> +		    {
> +		      if (POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (def1)))
> +			  && POINTER_TYPE_P
> +			  (TREE_TYPE (gimple_assign_rhs1 (def2))))

Better add temporaries for rhs1/2 of def1, then you don't have issues with
too long lines.

> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -10935,6 +10935,26 @@ Enable AddressSanitizer for Linux kernel.
>  See @uref{https://github.com/google/kasan/wiki} for more details.
>  The option cannot be combined with @option{-fcheck-pointer-bounds}.
>  
> +@item -fsanitize=pointer-compare
> +@opindex fsanitize=pointer-compare
> +Instrument comparison operation (<, <=, >, >=, -) with pointer operands.

Poiinter-compare doesn't instrument -, so ", -" should be left out.

> +The option cannot be combined with @option{-fsanitize=thread}
> +and/or @option{-fcheck-pointer-bounds}.
> +Note: By default the check is disabled at run time.  To enable it,
> +add @code{detect_invalid_pointer_pairs=1} to the environment variable
> +@env{ASAN_OPTIONS}.  The checking currently works only for pointers allocated
> +on heap.
> +
> +@item -fsanitize=subtract

-fsanitize=pointer-subtract

> +@opindex fsanitize=pointer-compare

s/compare/subtract/

> +Instrument subtraction with pointer operands.
> +The option cannot be combined with @option{-fsanitize=thread}
> +and/or @option{-fcheck-pointer-bounds}.
> +Note: By default the check is disabled at run time.  To enable it,
> +add @code{detect_invalid_pointer_pairs=1} to the environment variable
> +@env{ASAN_OPTIONS}.  The checking currently works only for pointers allocated
> +on heap.
> +
>  @item -fsanitize=thread
>  @opindex fsanitize=thread
>  Enable ThreadSanitizer, a fast data race detector.

Conceptually, these two instrumentations rely on address sanitization,
not really sure if we should supporting them for kernel sanitization (but I
bet it is just going to be too costly for kernel).
So, we also need to make sure at least parts of SANITIZE_ADDRESS is enabled
when these options are on.
That can be done by erroring out if -fsanitize=pointer-compare is requested
without -fsanitize=address, or by implicitly enabling -fsanitize=address for
these, or by adding yet another SANITIZE_* bit which would cover
sanitization of memory accesses for asan, that bit would be set by
-fsanitize={address,kernel-address} in addition to the current 2 bits, but
pointer-{subtract,compare} would set its own bit and SANITIZE_ADDRESS and
SANITIZE_USER_ADDRESS only.  Without the new bit we'd emit red zones,
function prologue/epilogue asan changes, registraction of global variables,
but not actual instrumentation of memory accesses (and probably not
instrumentation of C++ ctor ordering).

> --- a/gcc/gcc.c
> +++ b/gcc/gcc.c
> @@ -971,7 +971,9 @@ proper position among the other output files.  */
>  /* Linker command line options for -fsanitize= early on the command line.  */
>  #ifndef SANITIZER_EARLY_SPEC
>  #define SANITIZER_EARLY_SPEC "\
> -%{!nostdlib:%{!nodefaultlibs:%{%:sanitize(address):" LIBASAN_EARLY_SPEC "} \
> +%{!nostdlib:%{!nodefaultlibs:%{%:sanitize(address)\
> +    |%:sanitize(pointer-compare)\
> +    |%:sanitize(pointer-subtract):" LIBASAN_EARLY_SPEC "} \
>      %{%:sanitize(thread):" LIBTSAN_EARLY_SPEC "} \
>      %{%:sanitize(leak):" LIBLSAN_EARLY_SPEC "}}}"
>  #endif

Depending on the above, I wonder if sanitize(address) shouldn't be implied
by the new -fsanitize=pointer-{subtract,compare}, rather than polluting
specs for it everywhere.


> -      if (flag_sanitize & SANITIZE_ADDRESS)
> +      if (flag_sanitize & SANITIZE_ADDRESS
> +	  || flag_sanitize & SANITIZE_POINTER_COMPARE
> +	  || flag_sanitize & SANITIZE_POINTER_SUBTRACT)

Style nit, that would be better written as flag_sanitize & (A | B | C).
But depending on the above it might be simpler.

In any case, I'm not sure if we want this patch in GCC until the library
side isn't in so sorry state, because as we've discussed in the past, it
isn't really usable.  At least my reading of it is that whenever you
do one of these pointer comparisons or pointer subtractions and one or both
of the pointers aren't heap allocated, you'll get a runtime error.

	Jakub
Martin Liška Oct. 11, 2017, 5:55 a.m. UTC | #2
On 10/06/2017 03:33 PM, Jakub Jelinek wrote:
> On Fri, Oct 06, 2017 at 02:46:05PM +0200, Martin Liška wrote:
>> +	  if (sanitize_comparison_p)
>> +	    {
>> +	      if (is_gimple_assign (s)
>> +		  && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS
>> +		  && POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (s)))
>> +		  && POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs2 (s)))
>> +		  && is_pointer_compare_opcode (gimple_assign_rhs_code (s)))
> 

Hello.

> Isn't it better to test is_pointer_compare_opcode right after
> is_gimple_assign and leave the gimple_assign_rhs_class test out?

Yes, it is :)

> 
>> +		{
>> +		  ptr1 = gimple_assign_rhs1 (s);
>> +		  ptr2 = gimple_assign_rhs2 (s);
>> +		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
>> +		}
>> +	      else if (gimple_code (s) == GIMPLE_COND
>> +		       && POINTER_TYPE_P (TREE_TYPE (gimple_cond_lhs (s)))
>> +		       && POINTER_TYPE_P (TREE_TYPE (gimple_cond_rhs (s)))
>> +		       && is_pointer_compare_opcode (gimple_cond_code (s)))
>> +		{
>> +		  ptr1 = gimple_cond_lhs (s);
>> +		  ptr2 = gimple_cond_rhs (s);
>> +		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
>> +		}
> 
> You don't handle the case when there is a COND_EXPR with pointer comparison
> in the condition.

Good point, fixed.

> 
>> +	    }
>> +
>> +	  if (sanitize_subtraction_p
>> +	      && is_gimple_assign (s)
>> +	      && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS
> 
> The above isn't really needed.
> 
>> +	      && gimple_assign_rhs_code (s) == MINUS_EXPR)
>> +	    {
>> +	      tree rhs1 = gimple_assign_rhs1 (s);
>> +	      tree rhs2 = gimple_assign_rhs2 (s);
>> +
>> +	      if (TREE_CODE (rhs1) == SSA_NAME
>> +		  || TREE_CODE (rhs2) == SSA_NAME)
>> +		{
>> +		  gassign *def1
>> +		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs1));
>> +		  gassign *def2
>> +		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs2));
>> +
>> +		  if (def1 && def2
>> +		      && gimple_assign_rhs_class (def1) == GIMPLE_UNARY_RHS
>> +		      && gimple_assign_rhs_class (def2) == GIMPLE_UNARY_RHS)
>> +		    {
>> +		      if (POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (def1)))
>> +			  && POINTER_TYPE_P
>> +			  (TREE_TYPE (gimple_assign_rhs1 (def2))))
> 
> Better add temporaries for rhs1/2 of def1, then you don't have issues with
> too long lines.

Yes.

> 
>> --- a/gcc/doc/invoke.texi
>> +++ b/gcc/doc/invoke.texi
>> @@ -10935,6 +10935,26 @@ Enable AddressSanitizer for Linux kernel.
>>  See @uref{https://github.com/google/kasan/wiki} for more details.
>>  The option cannot be combined with @option{-fcheck-pointer-bounds}.
>>  
>> +@item -fsanitize=pointer-compare
>> +@opindex fsanitize=pointer-compare
>> +Instrument comparison operation (<, <=, >, >=, -) with pointer operands.
> 
> Poiinter-compare doesn't instrument -, so ", -" should be left out.

That's typo.

> 
>> +The option cannot be combined with @option{-fsanitize=thread}
>> +and/or @option{-fcheck-pointer-bounds}.
>> +Note: By default the check is disabled at run time.  To enable it,
>> +add @code{detect_invalid_pointer_pairs=1} to the environment variable
>> +@env{ASAN_OPTIONS}.  The checking currently works only for pointers allocated
>> +on heap.
>> +
>> +@item -fsanitize=subtract
> 
> -fsanitize=pointer-subtract
> 
>> +@opindex fsanitize=pointer-compare
> 
> s/compare/subtract/

Likewise.

> 
>> +Instrument subtraction with pointer operands.
>> +The option cannot be combined with @option{-fsanitize=thread}
>> +and/or @option{-fcheck-pointer-bounds}.
>> +Note: By default the check is disabled at run time.  To enable it,
>> +add @code{detect_invalid_pointer_pairs=1} to the environment variable
>> +@env{ASAN_OPTIONS}.  The checking currently works only for pointers allocated
>> +on heap.
>> +
>>  @item -fsanitize=thread
>>  @opindex fsanitize=thread
>>  Enable ThreadSanitizer, a fast data race detector.
> 
> Conceptually, these two instrumentations rely on address sanitization,
> not really sure if we should supporting them for kernel sanitization (but I
> bet it is just going to be too costly for kernel).
> So, we also need to make sure at least parts of SANITIZE_ADDRESS is enabled
> when these options are on.
> That can be done by erroring out if -fsanitize=pointer-compare is requested
> without -fsanitize=address, or by implicitly enabling -fsanitize=address for
> these, or by adding yet another SANITIZE_* bit which would cover
> sanitization of memory accesses for asan, that bit would be set by
> -fsanitize={address,kernel-address} in addition to the current 2 bits, but
> pointer-{subtract,compare} would set its own bit and SANITIZE_ADDRESS and
> SANITIZE_USER_ADDRESS only.  Without the new bit we'd emit red zones,
> function prologue/epilogue asan changes, registraction of global variables,
> but not actual instrumentation of memory accesses (and probably not
> instrumentation of C++ ctor ordering).

Agree, would be much easier to just enable SANITIZE_ADDRESS with there 2 options.
Question is how to make it also possible with -fsanitize=kernel-address:

$ ./xgcc -B.   /home/marxin/Programming/gcc/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c -fsanitize=pointer-compare,kernel-address
cc1: error: ‘-fsanitize=address’ is incompatible with ‘-fsanitize=kernel-address’

Ideas?

> 
>> --- a/gcc/gcc.c
>> +++ b/gcc/gcc.c
>> @@ -971,7 +971,9 @@ proper position among the other output files.  */
>>  /* Linker command line options for -fsanitize= early on the command line.  */
>>  #ifndef SANITIZER_EARLY_SPEC
>>  #define SANITIZER_EARLY_SPEC "\
>> -%{!nostdlib:%{!nodefaultlibs:%{%:sanitize(address):" LIBASAN_EARLY_SPEC "} \
>> +%{!nostdlib:%{!nodefaultlibs:%{%:sanitize(address)\
>> +    |%:sanitize(pointer-compare)\
>> +    |%:sanitize(pointer-subtract):" LIBASAN_EARLY_SPEC "} \
>>      %{%:sanitize(thread):" LIBTSAN_EARLY_SPEC "} \
>>      %{%:sanitize(leak):" LIBLSAN_EARLY_SPEC "}}}"
>>  #endif
> 
> Depending on the above, I wonder if sanitize(address) shouldn't be implied
> by the new -fsanitize=pointer-{subtract,compare}, rather than polluting
> specs for it everywhere.

Agree.

> 
> 
>> -      if (flag_sanitize & SANITIZE_ADDRESS)
>> +      if (flag_sanitize & SANITIZE_ADDRESS
>> +	  || flag_sanitize & SANITIZE_POINTER_COMPARE
>> +	  || flag_sanitize & SANITIZE_POINTER_SUBTRACT)
> 
> Style nit, that would be better written as flag_sanitize & (A | B | C).
> But depending on the above it might be simpler.

Will not be needed as now we enable SANITIZE_ADDRESS.

> 
> In any case, I'm not sure if we want this patch in GCC until the library
> side isn't in so sorry state, because as we've discussed in the past, it
> isn't really usable.  At least my reading of it is that whenever you
> do one of these pointer comparisons or pointer subtractions and one or both
> of the pointers aren't heap allocated, you'll get a runtime error.

Fully agree, I will send libsanitizer bits to LLVM as soon as we're happy with
the shape of the patch.

Martin

> 
> 	Jakub
>
From e43a7424cacde7f3cb7d3c1a481f9642d9cc8d95 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 5 Oct 2017 12:14:25 +0200
Subject: [PATCH] Add -fsanitize=pointer-{compare,subtract}.

gcc/ChangeLog:

2017-10-06  Martin Liska  <mliska@suse.cz>

	* asan.c (is_pointer_compare_opcode): New function.
	(instrument_pointer_comparison): Likewise.
	(asan_instrument): Handle SANITIZE_POINTER_COMPARE and
	SANITIZE_POINTER_SUBTRACT.
	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE):
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.

gcc/testsuite/ChangeLog:

2017-10-06  Martin Liska  <mliska@suse.cz>

	* gcc.dg/asan/pointer-compare-1.c: New test.
	* gcc.dg/asan/pointer-compare-2.c: New test.
	* gcc.dg/asan/pointer-subtract-1.c: New test.
	* gcc.dg/asan/pointer-subtract-2.c: New test.
---
 gcc/asan.c                                     | 119 +++++++++++++++++++++++++
 gcc/doc/invoke.texi                            |  18 ++++
 gcc/flag-types.h                               |   2 +
 gcc/opts.c                                     |   4 +
 gcc/sanitizer.def                              |   4 +
 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c  |  40 +++++++++
 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c  |  41 +++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c |  41 +++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c |  32 +++++++
 libsanitizer/asan/asan_descriptions.cc         |   9 ++
 libsanitizer/asan/asan_descriptions.h          |   1 +
 libsanitizer/asan/asan_globals.cc              |  25 ++++++
 libsanitizer/asan/asan_report.cc               |  52 +++++++++--
 libsanitizer/asan/asan_report.h                |   1 +
 libsanitizer/asan/asan_thread.cc               |  23 +++++
 libsanitizer/asan/asan_thread.h                |   1 +
 16 files changed, 406 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c

diff --git a/gcc/asan.c b/gcc/asan.c
index 2aa0a795af2..6bd437e0228 100644
--- a/gcc/asan.c
+++ b/gcc/asan.c
@@ -2370,6 +2370,122 @@ maybe_instrument_call (gimple_stmt_iterator *iter)
   return instrumented;
 }
 
+/* Return true if a given opcode CODE is potentially a non-valid comparison
+   of pointer types.  */
+
+static bool
+is_pointer_compare_opcode (tree_code code)
+{
+  return (code == LE_EXPR || code == LT_EXPR || code == GE_EXPR
+	  || code == GT_EXPR);
+}
+
+/* Instrument potential invalid operation executed on pointer types:
+   comparison different from != and == and subtraction of pointers.  */
+
+static void
+instrument_pointer_comparison (void)
+{
+  basic_block bb;
+  gimple_stmt_iterator i;
+
+  bool sanitize_comparison_p = sanitize_flags_p (SANITIZE_POINTER_COMPARE);
+  bool sanitize_subtraction_p = sanitize_flags_p (SANITIZE_POINTER_SUBTRACT);
+
+  FOR_EACH_BB_FN (bb, cfun)
+    {
+      for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
+	{
+	  gimple *s = gsi_stmt (i);
+
+	  tree ptr1 = NULL_TREE;
+	  tree ptr2 = NULL_TREE;
+	  enum built_in_function fn = BUILT_IN_NONE;
+
+	  if (sanitize_comparison_p)
+	    {
+	      tree cond_expr, rhs1, rhs2, lhs, rhs;
+
+	      if (is_gimple_assign (s)
+		  && is_pointer_compare_opcode (gimple_assign_rhs_code (s))
+		  && (rhs1 = gimple_assign_rhs1 (s))
+		  && (rhs2 = gimple_assign_rhs2 (s))
+		  && POINTER_TYPE_P (TREE_TYPE (rhs1))
+		  && POINTER_TYPE_P (TREE_TYPE (rhs2)))
+		{
+		  ptr1 = rhs1;
+		  ptr2 = rhs2;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	      else if (is_gimple_assign (s)
+		       && gimple_assign_rhs_code (s) == COND_EXPR
+		       && (cond_expr = gimple_assign_rhs1 (s))
+		       && is_pointer_compare_opcode (TREE_CODE (cond_expr))
+		       && (rhs1 = TREE_OPERAND (cond_expr, 0))
+		       && (rhs2 = TREE_OPERAND (cond_expr, 1))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs1))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs2)))
+		{
+		  ptr1 = rhs1;
+		  ptr2 = rhs2;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	      else if (gimple_code (s) == GIMPLE_COND
+		       && (lhs = gimple_cond_lhs (s))
+		       && (rhs = gimple_cond_rhs (s))
+		       && POINTER_TYPE_P (TREE_TYPE (lhs))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs))
+		       && is_pointer_compare_opcode (gimple_cond_code (s)))
+		{
+		  ptr1 = rhs;
+		  ptr2 = rhs;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	    }
+
+	  if (sanitize_subtraction_p
+	      && is_gimple_assign (s)
+	      && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS
+	      && gimple_assign_rhs_code (s) == MINUS_EXPR)
+	    {
+	      tree rhs1 = gimple_assign_rhs1 (s);
+	      tree rhs2 = gimple_assign_rhs2 (s);
+
+	      if (TREE_CODE (rhs1) == SSA_NAME
+		  && TREE_CODE (rhs2) == SSA_NAME)
+		{
+		  gassign *def1
+		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs1));
+		  gassign *def2
+		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs2));
+
+		  if (def1 && def2
+		      && gimple_assign_rhs_class (def1) == GIMPLE_UNARY_RHS
+		      && gimple_assign_rhs_class (def2) == GIMPLE_UNARY_RHS)
+		    {
+		      if (POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (def1)))
+			  && POINTER_TYPE_P
+			  (TREE_TYPE (gimple_assign_rhs1 (def2))))
+			{
+			  ptr1 = rhs1;
+			  ptr2 = rhs2;
+			  fn = BUILT_IN_ASAN_POINTER_SUBTRACT;
+			}
+		    }
+		}
+	    }
+
+	  if (ptr1 != NULL_TREE && ptr2 != NULL_TREE)
+	    {
+	      tree decl = builtin_decl_implicit (fn);
+	      gimple *g = gimple_build_call (decl, 2, ptr1, ptr2);
+	      gimple_set_location (g, gimple_location (s));
+	      gsi_insert_before (&i, g, GSI_SAME_STMT);
+	    }
+	}
+    }
+}
+
 /* Walk each instruction of all basic block and instrument those that
    represent memory references: loads, stores, or function calls.
    In a given basic block, this function avoids instrumenting memory
@@ -3432,6 +3548,9 @@ asan_instrument (void)
 {
   if (shadow_ptr_types[0] == NULL_TREE)
     asan_init_shadow_ptr_types ();
+
+  if (sanitize_flags_p (SANITIZE_POINTER_COMPARE | SANITIZE_POINTER_SUBTRACT))
+    instrument_pointer_comparison ();
   transform_statements ();
   last_alloca_addr = NULL_TREE;
   return 0;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 9ad1fb339ba..e49aef5f6ba 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10955,6 +10955,24 @@ Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 1f439d35b07..74464651e00 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_VPTR = 1UL << 22,
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
+  SANITIZE_POINTER_COMPARE = 1UL << 25,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 26,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/opts.c b/gcc/opts.c
index 5aa5d066dbe..f544484cf26 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -1496,6 +1496,10 @@ const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, (SANITIZE_POINTER_COMPARE | SANITIZE_ADDRESS
+				   | SANITIZE_USER_ADDRESS), true),
+  SANITIZER_OPT (pointer-subtract, (SANITIZE_POINTER_SUBTRACT | SANITIZE_ADDRESS
+				   | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 9d963f05c21..d06f68ba66e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..8b842c5567e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
@@ -0,0 +1,40 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p, char *q)
+{
+  return p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..0e0d3589a30
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = foo(p);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[1]);
+  bar(&global[1], &global[2]);
+  bar(&global[2], &global[1]);
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..10792264f2e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int foo(char *p, char *q)
+{
+  return p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
+
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..17cf0e6711d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
@@ -0,0 +1,32 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = bar(p, p - 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/libsanitizer/asan/asan_descriptions.cc b/libsanitizer/asan/asan_descriptions.cc
index 35d1619f2d9..eaa04378c2c 100644
--- a/libsanitizer/asan/asan_descriptions.cc
+++ b/libsanitizer/asan/asan_descriptions.cc
@@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
   return true;
 }
 
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
+{
+  AsanThread *t = FindThreadByStackAddress(addr);
+  if (!t) return false;
+
+  *shadow_addr = t->GetStackFrameVariableBeginning (addr);
+  return true;
+}
+
 static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
                                           uptr access_size, uptr prev_var_end,
                                           uptr next_var_beg) {
diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
index 584b9ba6491..b7f23b1a71b 100644
--- a/libsanitizer/asan/asan_descriptions.h
+++ b/libsanitizer/asan/asan_descriptions.h
@@ -138,6 +138,7 @@ struct StackAddressDescription {
 
 bool GetStackAddressInformation(uptr addr, uptr access_size,
                                 StackAddressDescription *descr);
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
 
 struct GlobalAddressDescription {
   uptr addr;
diff --git a/libsanitizer/asan/asan_globals.cc b/libsanitizer/asan/asan_globals.cc
index f2292926e6a..ed707c0ca01 100644
--- a/libsanitizer/asan/asan_globals.cc
+++ b/libsanitizer/asan/asan_globals.cc
@@ -122,6 +122,31 @@ int GetGlobalsForAddress(uptr addr, Global *globals, u32 *reg_sites,
   return res;
 }
 
+bool AreGlobalVariablesSame(uptr addr1, uptr addr2)
+{
+  if (addr1 > addr2)
+  {
+    uptr tmp = addr1;
+    addr1 = addr2;
+    addr2 = tmp;
+  }
+
+  BlockingMutexLock lock(&mu_for_globals);
+
+  uptr aligned_addr1 = addr1 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  uptr aligned_addr2 = addr2 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+
+  u8 *shadow_ptr1 = (u8*)MemToShadow(aligned_addr1);
+  u8 *shadow_ptr2 = (u8*)MemToShadow(aligned_addr2);
+
+  while (shadow_ptr1 <= shadow_ptr2
+	 && *shadow_ptr1 != kAsanGlobalRedzoneMagic) {
+    shadow_ptr1++;
+  }
+
+  return shadow_ptr1 == shadow_ptr2;
+}
+
 enum GlobalSymbolState {
   UNREGISTERED = 0,
   REGISTERED = 1
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 84d67646b40..44fe95e163d 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -344,14 +344,52 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
-    GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+
+  if (a1 == a2)
+    return;
+
+  uptr shadow_offset1, shadow_offset2;
+  bool valid1, valid2;
+  {
+    ThreadRegistryLock l(&asanThreadRegistry());
+
+    valid1 = GetStackVariableBeginning(a1, &shadow_offset1);
+    valid2 = GetStackVariableBeginning(a2, &shadow_offset2);
+  }
+
+  if (valid1 && valid2) {
+    if (shadow_offset1 == shadow_offset2)
+      return;
   }
+  else if (!valid1 && !valid2) {
+    AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
+    AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
+    valid1 = chunk1.IsAllocated();
+    valid2 = chunk2.IsAllocated();
+
+    if (valid1 && valid2) {
+      if (chunk1.Eq(chunk2))
+	return;
+    }
+    else if (!valid1 && !valid2) {
+      uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
+      if (offset <= 2048) {
+	if (AreGlobalVariablesSame (a1, a2))
+	  return;
+      }
+
+      GlobalAddressDescription gdesc1, gdesc2;
+      valid1 = GetGlobalAddressInformation(a1, 1, &gdesc1);
+      valid2 = GetGlobalAddressInformation(a2, 1, &gdesc2);
+
+      if (valid1 && valid2
+	  && gdesc1.globals[0].beg == gdesc2.globals[0].beg)
+	return;
+    }
+  }
+
+  GET_CALLER_PC_BP_SP;
+  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
 
diff --git a/libsanitizer/asan/asan_report.h b/libsanitizer/asan/asan_report.h
index 111b8400153..2b4c9d29fda 100644
--- a/libsanitizer/asan/asan_report.h
+++ b/libsanitizer/asan/asan_report.h
@@ -27,6 +27,7 @@ struct StackVarDescr {
 // them to "globals" array.
 int GetGlobalsForAddress(uptr addr, __asan_global *globals, u32 *reg_sites,
                          int max_globals);
+bool AreGlobalVariablesSame(uptr addr1, uptr addr2);
 
 const char *MaybeDemangleGlobalName(const char *name);
 void PrintGlobalNameIfASCII(InternalScopedString *str, const __asan_global &g);
diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
index 818e1261400..d6bd051a493 100644
--- a/libsanitizer/asan/asan_thread.cc
+++ b/libsanitizer/asan/asan_thread.cc
@@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
   return true;
 }
 
+uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
+{
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  }
+  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom &&
+         (*shadow_ptr != kAsanStackLeftRedzoneMagic
+	  && *shadow_ptr != kAsanStackMidRedzoneMagic
+	  && *shadow_ptr != kAsanStackRightRedzoneMagic)) {
+    shadow_ptr--;
+  }
+
+  return (uptr)shadow_ptr;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
index c51a58ad0bb..c5adecacad4 100644
--- a/libsanitizer/asan/asan_thread.h
+++ b/libsanitizer/asan/asan_thread.h
@@ -80,6 +80,7 @@ class AsanThread {
     const char *frame_descr;
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
+  uptr GetStackFrameVariableBeginning(uptr addr);
 
   bool AddrIsInStack(uptr addr);
Jakub Jelinek Oct. 11, 2017, 7:37 a.m. UTC | #3
On Wed, Oct 11, 2017 at 07:55:44AM +0200, Martin Liška wrote:
> > Conceptually, these two instrumentations rely on address sanitization,
> > not really sure if we should supporting them for kernel sanitization (but I
> > bet it is just going to be too costly for kernel).
> > So, we also need to make sure at least parts of SANITIZE_ADDRESS is enabled
> > when these options are on.
> > That can be done by erroring out if -fsanitize=pointer-compare is requested
> > without -fsanitize=address, or by implicitly enabling -fsanitize=address for
> > these, or by adding yet another SANITIZE_* bit which would cover
> > sanitization of memory accesses for asan, that bit would be set by
> > -fsanitize={address,kernel-address} in addition to the current 2 bits, but
> > pointer-{subtract,compare} would set its own bit and SANITIZE_ADDRESS and
> > SANITIZE_USER_ADDRESS only.  Without the new bit we'd emit red zones,
> > function prologue/epilogue asan changes, registraction of global variables,
> > but not actual instrumentation of memory accesses (and probably not
> > instrumentation of C++ ctor ordering).
> 
> Agree, would be much easier to just enable SANITIZE_ADDRESS with there 2 options.
> Question is how to make it also possible with -fsanitize=kernel-address:
> 
> $ ./xgcc -B.   /home/marxin/Programming/gcc/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c -fsanitize=pointer-compare,kernel-address
> cc1: error: ‘-fsanitize=address’ is incompatible with ‘-fsanitize=kernel-address’
> 
> Ideas?

If we want to make it usable for both user and kernel address, then either
we'll let pointer-compare/pointer-subtract implicitly enable user address,
unless kernel-address has been enabled (that would mean set SANITIZE_ADDRESS
bit in pointer-*, but not SANITIZE_USER_ADDRESS, and at the point where we
diagnose option incompatibilities like -fsanitize=address,kernel-address
check for the case (SANITIZE_ADDRESS bit set, none of SANITIZE_USER_ADDRESS
nor SANITIZE_KERNEL_ADDRESS, and one of SANITIZE_POINTER_*) and set
implicitly SANITIZE_USER_ADDRESS, or simply require that the user chooses,
by erroring out if pointer-* is used without explicit address or
kernel-address.  In any case, I think this should be also something
discussed with the upstream sanitizer folks, so that LLVM (if it ever
decides to actually implement it) behaves compatibly.

> --- a/libsanitizer/asan/asan_descriptions.cc
> +++ b/libsanitizer/asan/asan_descriptions.cc
> @@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
>    return true;
>  }
>  
> +bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
> +{
> +  AsanThread *t = FindThreadByStackAddress(addr);
> +  if (!t) return false;
> +
> +  *shadow_addr = t->GetStackFrameVariableBeginning (addr);
> +  return true;
> +}
> +
>  static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
>                                            uptr access_size, uptr prev_var_end,
>                                            uptr next_var_beg) {
> diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
> index 584b9ba6491..b7f23b1a71b 100644
> --- a/libsanitizer/asan/asan_descriptions.h
> +++ b/libsanitizer/asan/asan_descriptions.h
> @@ -138,6 +138,7 @@ struct StackAddressDescription {
>  
>  bool GetStackAddressInformation(uptr addr, uptr access_size,
>                                  StackAddressDescription *descr);
> +bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
>  
>  struct GlobalAddressDescription {
>    uptr addr;
> diff --git a/libsanitizer/asan/asan_globals.cc b/libsanitizer/asan/asan_globals.cc
> index f2292926e6a..ed707c0ca01 100644
> --- a/libsanitizer/asan/asan_globals.cc
> +++ b/libsanitizer/asan/asan_globals.cc
> @@ -122,6 +122,31 @@ int GetGlobalsForAddress(uptr addr, Global *globals, u32 *reg_sites,
>    return res;
>  }
>  
> +bool AreGlobalVariablesSame(uptr addr1, uptr addr2)
> +{
> +  if (addr1 > addr2)
> +  {
> +    uptr tmp = addr1;
> +    addr1 = addr2;
> +    addr2 = tmp;

std::swap(addr1, addr2); ?  I don't see it used in any of libsanitizer
though, so not sure if the corresponding STL header is included.

> +  }
> +
> +  BlockingMutexLock lock(&mu_for_globals);

Why do you need a mutex for checking if there are no red zones in between?

> +  uptr aligned_addr1 = addr1 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
> +  uptr aligned_addr2 = addr2 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
> +
> +  u8 *shadow_ptr1 = (u8*)MemToShadow(aligned_addr1);
> +  u8 *shadow_ptr2 = (u8*)MemToShadow(aligned_addr2);
> +
> +  while (shadow_ptr1 <= shadow_ptr2
> +	 && *shadow_ptr1 != kAsanGlobalRedzoneMagic) {
> +    shadow_ptr1++;
> +  }

There are many kinds of shadow memory markings.  My thought was that it
would start with a quick check, perhaps vectorized by hand (depending on if
the arch has unaligned loads maybe without or with a short loop for
alignment) where say unsigned long (perhaps may_alias?) pointer would be
used to read 4/8 shadow bytes at a time, and just check if any of them is
non-zero.  And, if it found a non-zero byte, deal with it after the loop,
if after the "vectorized" loop, find which byte it was, and in any case
deal e.g. with the various special cases like when the shadow byte is 1-7
and stands for how many bytes are accessible, etc.

> --- a/libsanitizer/asan/asan_report.cc
> +++ b/libsanitizer/asan/asan_report.cc
> @@ -344,14 +344,52 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
>    if (!flags()->detect_invalid_pointer_pairs) return;
>    uptr a1 = reinterpret_cast<uptr>(p1);
>    uptr a2 = reinterpret_cast<uptr>(p2);
> -  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
> -  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
> -  bool valid1 = chunk1.IsAllocated();
> -  bool valid2 = chunk2.IsAllocated();
> -  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
> -    GET_CALLER_PC_BP_SP;
> -    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
> +
> +  if (a1 == a2)
> +    return;

My thought was that you'd do the difference <= 2048 test first
without any lock, and only for the larger stuff take locks and look stuff
up.

> +
> +  uptr shadow_offset1, shadow_offset2;
> +  bool valid1, valid2;
> +  {
> +    ThreadRegistryLock l(&asanThreadRegistry());
> +
> +    valid1 = GetStackVariableBeginning(a1, &shadow_offset1);
> +    valid2 = GetStackVariableBeginning(a2, &shadow_offset2);
> +  }
> +
> +  if (valid1 && valid2) {
> +    if (shadow_offset1 == shadow_offset2)
> +      return;


>    }
> +  else if (!valid1 && !valid2) {
> +    AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
> +    AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
> +    valid1 = chunk1.IsAllocated();
> +    valid2 = chunk2.IsAllocated();
> +
> +    if (valid1 && valid2) {
> +      if (chunk1.Eq(chunk2))
> +	return;
> +    }
> +    else if (!valid1 && !valid2) {
> +      uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
> +      if (offset <= 2048) {
> +	if (AreGlobalVariablesSame (a1, a2))
> +	  return;
> +      }
> +
> +      GlobalAddressDescription gdesc1, gdesc2;
> +      valid1 = GetGlobalAddressInformation(a1, 1, &gdesc1);
> +      valid2 = GetGlobalAddressInformation(a2, 1, &gdesc2);
> +
> +      if (valid1 && valid2
> +	  && gdesc1.globals[0].beg == gdesc2.globals[0].beg)
> +	return;
> +    }
> +  }
> +
> +  GET_CALLER_PC_BP_SP;
> +  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
>  }
>  // ----------------------- Mac-specific reports ----------------- {{{1
>  
> diff --git a/libsanitizer/asan/asan_report.h b/libsanitizer/asan/asan_report.h
> index 111b8400153..2b4c9d29fda 100644
> --- a/libsanitizer/asan/asan_report.h
> +++ b/libsanitizer/asan/asan_report.h
> @@ -27,6 +27,7 @@ struct StackVarDescr {
>  // them to "globals" array.
>  int GetGlobalsForAddress(uptr addr, __asan_global *globals, u32 *reg_sites,
>                           int max_globals);
> +bool AreGlobalVariablesSame(uptr addr1, uptr addr2);
>  
>  const char *MaybeDemangleGlobalName(const char *name);
>  void PrintGlobalNameIfASCII(InternalScopedString *str, const __asan_global &g);
> diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
> index 818e1261400..d6bd051a493 100644
> --- a/libsanitizer/asan/asan_thread.cc
> +++ b/libsanitizer/asan/asan_thread.cc
> @@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
>    return true;
>  }
>  
> +uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
> +{
> +  uptr bottom = 0;
> +  if (AddrIsInStack(addr)) {
> +    bottom = stack_bottom();
> +  } else if (has_fake_stack()) {
> +    bottom = fake_stack()->AddrIsInFakeStack(addr);
> +    CHECK(bottom);
> +  }
> +  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
> +  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
> +  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
> +
> +  while (shadow_ptr >= shadow_bottom &&
> +         (*shadow_ptr != kAsanStackLeftRedzoneMagic
> +	  && *shadow_ptr != kAsanStackMidRedzoneMagic
> +	  && *shadow_ptr != kAsanStackRightRedzoneMagic)) {
> +    shadow_ptr--;
> +  }
> +
> +  return (uptr)shadow_ptr;
> +}
> +
>  bool AsanThread::AddrIsInStack(uptr addr) {
>    const auto bounds = GetStackBounds();
>    return addr >= bounds.bottom && addr < bounds.top;
> diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
> index c51a58ad0bb..c5adecacad4 100644
> --- a/libsanitizer/asan/asan_thread.h
> +++ b/libsanitizer/asan/asan_thread.h
> @@ -80,6 +80,7 @@ class AsanThread {
>      const char *frame_descr;
>    };
>    bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
> +  uptr GetStackFrameVariableBeginning(uptr addr);
>  
>    bool AddrIsInStack(uptr addr);
>  
> -- 
> 2.14.2
> 


	Jakub
Martin Liška Oct. 11, 2017, 1:36 p.m. UTC | #4
On 10/11/2017 09:37 AM, Jakub Jelinek wrote:
> On Wed, Oct 11, 2017 at 07:55:44AM +0200, Martin Liška wrote:
>>> Conceptually, these two instrumentations rely on address sanitization,
>>> not really sure if we should supporting them for kernel sanitization (but I
>>> bet it is just going to be too costly for kernel).
>>> So, we also need to make sure at least parts of SANITIZE_ADDRESS is enabled
>>> when these options are on.
>>> That can be done by erroring out if -fsanitize=pointer-compare is requested
>>> without -fsanitize=address, or by implicitly enabling -fsanitize=address for
>>> these, or by adding yet another SANITIZE_* bit which would cover
>>> sanitization of memory accesses for asan, that bit would be set by
>>> -fsanitize={address,kernel-address} in addition to the current 2 bits, but
>>> pointer-{subtract,compare} would set its own bit and SANITIZE_ADDRESS and
>>> SANITIZE_USER_ADDRESS only.  Without the new bit we'd emit red zones,
>>> function prologue/epilogue asan changes, registraction of global variables,
>>> but not actual instrumentation of memory accesses (and probably not
>>> instrumentation of C++ ctor ordering).
>>
>> Agree, would be much easier to just enable SANITIZE_ADDRESS with there 2 options.
>> Question is how to make it also possible with -fsanitize=kernel-address:
>>
>> $ ./xgcc -B.   /home/marxin/Programming/gcc/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c -fsanitize=pointer-compare,kernel-address
>> cc1: error: ‘-fsanitize=address’ is incompatible with ‘-fsanitize=kernel-address’
>>
>> Ideas?
> 

Hello.

Thanks for feedback.

> If we want to make it usable for both user and kernel address, then either
> we'll let pointer-compare/pointer-subtract implicitly enable user address,
> unless kernel-address has been enabled (that would mean set SANITIZE_ADDRESS
> bit in pointer-*, but not SANITIZE_USER_ADDRESS, and at the point where we
> diagnose option incompatibilities like -fsanitize=address,kernel-address
> check for the case (SANITIZE_ADDRESS bit set, none of SANITIZE_USER_ADDRESS
> nor SANITIZE_KERNEL_ADDRESS, and one of SANITIZE_POINTER_*) and set
> implicitly SANITIZE_USER_ADDRESS, or simply require that the user chooses,
> by erroring out if pointer-* is used without explicit address or
> kernel-address.  In any case, I think this should be also something
> discussed with the upstream sanitizer folks, so that LLVM (if it ever
> decides to actually implement it) behaves compatibly.

I've added support for automatic adding of -sanitize=address if none of them is added.
Problem is that LIBASAN_SPEC is still handled in driver. Thus I guess I'll need the hunks
I sent in first version of patch. Or do I miss something?


> 
>> --- a/libsanitizer/asan/asan_descriptions.cc
>> +++ b/libsanitizer/asan/asan_descriptions.cc
>> @@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
>>    return true;
>>  }
>>  
>> +bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
>> +{
>> +  AsanThread *t = FindThreadByStackAddress(addr);
>> +  if (!t) return false;
>> +
>> +  *shadow_addr = t->GetStackFrameVariableBeginning (addr);
>> +  return true;
>> +}
>> +
>>  static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
>>                                            uptr access_size, uptr prev_var_end,
>>                                            uptr next_var_beg) {
>> diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
>> index 584b9ba6491..b7f23b1a71b 100644
>> --- a/libsanitizer/asan/asan_descriptions.h
>> +++ b/libsanitizer/asan/asan_descriptions.h
>> @@ -138,6 +138,7 @@ struct StackAddressDescription {
>>  
>>  bool GetStackAddressInformation(uptr addr, uptr access_size,
>>                                  StackAddressDescription *descr);
>> +bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
>>  
>>  struct GlobalAddressDescription {
>>    uptr addr;
>> diff --git a/libsanitizer/asan/asan_globals.cc b/libsanitizer/asan/asan_globals.cc
>> index f2292926e6a..ed707c0ca01 100644
>> --- a/libsanitizer/asan/asan_globals.cc
>> +++ b/libsanitizer/asan/asan_globals.cc
>> @@ -122,6 +122,31 @@ int GetGlobalsForAddress(uptr addr, Global *globals, u32 *reg_sites,
>>    return res;
>>  }
>>  
>> +bool AreGlobalVariablesSame(uptr addr1, uptr addr2)
>> +{
>> +  if (addr1 > addr2)
>> +  {
>> +    uptr tmp = addr1;
>> +    addr1 = addr2;
>> +    addr2 = tmp;
> 
> std::swap(addr1, addr2); ?  I don't see it used in any of libsanitizer
> though, so not sure if the corresponding STL header is included.

They don't use it anywhere and I had some #include issues. That's why I did it manually.

> 
>> +  }
>> +
>> +  BlockingMutexLock lock(&mu_for_globals);
> 
> Why do you need a mutex for checking if there are no red zones in between?
> 
>> +  uptr aligned_addr1 = addr1 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
>> +  uptr aligned_addr2 = addr2 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
>> +
>> +  u8 *shadow_ptr1 = (u8*)MemToShadow(aligned_addr1);
>> +  u8 *shadow_ptr2 = (u8*)MemToShadow(aligned_addr2);
>> +
>> +  while (shadow_ptr1 <= shadow_ptr2
>> +	 && *shadow_ptr1 != kAsanGlobalRedzoneMagic) {
>> +    shadow_ptr1++;
>> +  }
> 
> There are many kinds of shadow memory markings.  My thought was that it
> would start with a quick check, perhaps vectorized by hand (depending on if
> the arch has unaligned loads maybe without or with a short loop for

Did that, but I have no experience how to make decision about prologue that will
align the pointer? Any examples?

> alignment) where say unsigned long (perhaps may_alias?) pointer would be
> used to read 4/8 shadow bytes at a time, and just check if any of them is
> non-zero.  And, if it found a non-zero byte, deal with it after the loop,
> if after the "vectorized" loop, find which byte it was, and in any case
> deal e.g. with the various special cases like when the shadow byte is 1-7
> and stands for how many bytes are accessible, etc.

Yes, can help significantly I guess.

Martin

> 
>> --- a/libsanitizer/asan/asan_report.cc
>> +++ b/libsanitizer/asan/asan_report.cc
>> @@ -344,14 +344,52 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
>>    if (!flags()->detect_invalid_pointer_pairs) return;
>>    uptr a1 = reinterpret_cast<uptr>(p1);
>>    uptr a2 = reinterpret_cast<uptr>(p2);
>> -  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
>> -  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
>> -  bool valid1 = chunk1.IsAllocated();
>> -  bool valid2 = chunk2.IsAllocated();
>> -  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
>> -    GET_CALLER_PC_BP_SP;
>> -    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
>> +
>> +  if (a1 == a2)
>> +    return;
> 
> My thought was that you'd do the difference <= 2048 test first
> without any lock, and only for the larger stuff take locks and look stuff
> up.
> 
>> +
>> +  uptr shadow_offset1, shadow_offset2;
>> +  bool valid1, valid2;
>> +  {
>> +    ThreadRegistryLock l(&asanThreadRegistry());
>> +
>> +    valid1 = GetStackVariableBeginning(a1, &shadow_offset1);
>> +    valid2 = GetStackVariableBeginning(a2, &shadow_offset2);
>> +  }
>> +
>> +  if (valid1 && valid2) {
>> +    if (shadow_offset1 == shadow_offset2)
>> +      return;
> 
> 
>>    }
>> +  else if (!valid1 && !valid2) {
>> +    AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
>> +    AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
>> +    valid1 = chunk1.IsAllocated();
>> +    valid2 = chunk2.IsAllocated();
>> +
>> +    if (valid1 && valid2) {
>> +      if (chunk1.Eq(chunk2))
>> +	return;
>> +    }
>> +    else if (!valid1 && !valid2) {
>> +      uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
>> +      if (offset <= 2048) {
>> +	if (AreGlobalVariablesSame (a1, a2))
>> +	  return;
>> +      }
>> +
>> +      GlobalAddressDescription gdesc1, gdesc2;
>> +      valid1 = GetGlobalAddressInformation(a1, 1, &gdesc1);
>> +      valid2 = GetGlobalAddressInformation(a2, 1, &gdesc2);
>> +
>> +      if (valid1 && valid2
>> +	  && gdesc1.globals[0].beg == gdesc2.globals[0].beg)
>> +	return;
>> +    }
>> +  }
>> +
>> +  GET_CALLER_PC_BP_SP;
>> +  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
>>  }
>>  // ----------------------- Mac-specific reports ----------------- {{{1
>>  
>> diff --git a/libsanitizer/asan/asan_report.h b/libsanitizer/asan/asan_report.h
>> index 111b8400153..2b4c9d29fda 100644
>> --- a/libsanitizer/asan/asan_report.h
>> +++ b/libsanitizer/asan/asan_report.h
>> @@ -27,6 +27,7 @@ struct StackVarDescr {
>>  // them to "globals" array.
>>  int GetGlobalsForAddress(uptr addr, __asan_global *globals, u32 *reg_sites,
>>                           int max_globals);
>> +bool AreGlobalVariablesSame(uptr addr1, uptr addr2);
>>  
>>  const char *MaybeDemangleGlobalName(const char *name);
>>  void PrintGlobalNameIfASCII(InternalScopedString *str, const __asan_global &g);
>> diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
>> index 818e1261400..d6bd051a493 100644
>> --- a/libsanitizer/asan/asan_thread.cc
>> +++ b/libsanitizer/asan/asan_thread.cc
>> @@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
>>    return true;
>>  }
>>  
>> +uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
>> +{
>> +  uptr bottom = 0;
>> +  if (AddrIsInStack(addr)) {
>> +    bottom = stack_bottom();
>> +  } else if (has_fake_stack()) {
>> +    bottom = fake_stack()->AddrIsInFakeStack(addr);
>> +    CHECK(bottom);
>> +  }
>> +  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
>> +  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
>> +  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
>> +
>> +  while (shadow_ptr >= shadow_bottom &&
>> +         (*shadow_ptr != kAsanStackLeftRedzoneMagic
>> +	  && *shadow_ptr != kAsanStackMidRedzoneMagic
>> +	  && *shadow_ptr != kAsanStackRightRedzoneMagic)) {
>> +    shadow_ptr--;
>> +  }
>> +
>> +  return (uptr)shadow_ptr;
>> +}
>> +
>>  bool AsanThread::AddrIsInStack(uptr addr) {
>>    const auto bounds = GetStackBounds();
>>    return addr >= bounds.bottom && addr < bounds.top;
>> diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
>> index c51a58ad0bb..c5adecacad4 100644
>> --- a/libsanitizer/asan/asan_thread.h
>> +++ b/libsanitizer/asan/asan_thread.h
>> @@ -80,6 +80,7 @@ class AsanThread {
>>      const char *frame_descr;
>>    };
>>    bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
>> +  uptr GetStackFrameVariableBeginning(uptr addr);
>>  
>>    bool AddrIsInStack(uptr addr);
>>  
>> -- 
>> 2.14.2
>>
> 
> 
> 	Jakub
>
From 6bcf3d0064057edf55f0f65f7899b8a4a5e7e7dc Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 5 Oct 2017 12:14:25 +0200
Subject: [PATCH] Add -fsanitize=pointer-{compare,subtract}.

gcc/ChangeLog:

2017-10-06  Martin Liska  <mliska@suse.cz>

	* asan.c (is_pointer_compare_opcode): New function.
	(instrument_pointer_comparison): Likewise.
	(asan_instrument): Handle SANITIZE_POINTER_COMPARE and
	SANITIZE_POINTER_SUBTRACT.
	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE):
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.

gcc/testsuite/ChangeLog:

2017-10-06  Martin Liska  <mliska@suse.cz>

	* gcc.dg/asan/pointer-compare-1.c: New test.
	* gcc.dg/asan/pointer-compare-2.c: New test.
	* gcc.dg/asan/pointer-subtract-1.c: New test.
	* gcc.dg/asan/pointer-subtract-2.c: New test.
---
 gcc/asan.c                                     | 119 +++++++++++++++++++++++++
 gcc/doc/invoke.texi                            |  18 ++++
 gcc/flag-types.h                               |   2 +
 gcc/opts.c                                     |  10 +++
 gcc/sanitizer.def                              |   4 +
 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c  |  40 +++++++++
 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c  |  41 +++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c |  41 +++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c |  32 +++++++
 libsanitizer/asan/asan_descriptions.cc         |   9 ++
 libsanitizer/asan/asan_descriptions.h          |   1 +
 libsanitizer/asan/asan_globals.cc              |  29 ++++++
 libsanitizer/asan/asan_report.cc               |  52 +++++++++--
 libsanitizer/asan/asan_report.h                |   1 +
 libsanitizer/asan/asan_thread.cc               |  23 +++++
 libsanitizer/asan/asan_thread.h                |   1 +
 16 files changed, 416 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c

diff --git a/gcc/asan.c b/gcc/asan.c
index 2aa0a795af2..6bd437e0228 100644
--- a/gcc/asan.c
+++ b/gcc/asan.c
@@ -2370,6 +2370,122 @@ maybe_instrument_call (gimple_stmt_iterator *iter)
   return instrumented;
 }
 
+/* Return true if a given opcode CODE is potentially a non-valid comparison
+   of pointer types.  */
+
+static bool
+is_pointer_compare_opcode (tree_code code)
+{
+  return (code == LE_EXPR || code == LT_EXPR || code == GE_EXPR
+	  || code == GT_EXPR);
+}
+
+/* Instrument potential invalid operation executed on pointer types:
+   comparison different from != and == and subtraction of pointers.  */
+
+static void
+instrument_pointer_comparison (void)
+{
+  basic_block bb;
+  gimple_stmt_iterator i;
+
+  bool sanitize_comparison_p = sanitize_flags_p (SANITIZE_POINTER_COMPARE);
+  bool sanitize_subtraction_p = sanitize_flags_p (SANITIZE_POINTER_SUBTRACT);
+
+  FOR_EACH_BB_FN (bb, cfun)
+    {
+      for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
+	{
+	  gimple *s = gsi_stmt (i);
+
+	  tree ptr1 = NULL_TREE;
+	  tree ptr2 = NULL_TREE;
+	  enum built_in_function fn = BUILT_IN_NONE;
+
+	  if (sanitize_comparison_p)
+	    {
+	      tree cond_expr, rhs1, rhs2, lhs, rhs;
+
+	      if (is_gimple_assign (s)
+		  && is_pointer_compare_opcode (gimple_assign_rhs_code (s))
+		  && (rhs1 = gimple_assign_rhs1 (s))
+		  && (rhs2 = gimple_assign_rhs2 (s))
+		  && POINTER_TYPE_P (TREE_TYPE (rhs1))
+		  && POINTER_TYPE_P (TREE_TYPE (rhs2)))
+		{
+		  ptr1 = rhs1;
+		  ptr2 = rhs2;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	      else if (is_gimple_assign (s)
+		       && gimple_assign_rhs_code (s) == COND_EXPR
+		       && (cond_expr = gimple_assign_rhs1 (s))
+		       && is_pointer_compare_opcode (TREE_CODE (cond_expr))
+		       && (rhs1 = TREE_OPERAND (cond_expr, 0))
+		       && (rhs2 = TREE_OPERAND (cond_expr, 1))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs1))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs2)))
+		{
+		  ptr1 = rhs1;
+		  ptr2 = rhs2;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	      else if (gimple_code (s) == GIMPLE_COND
+		       && (lhs = gimple_cond_lhs (s))
+		       && (rhs = gimple_cond_rhs (s))
+		       && POINTER_TYPE_P (TREE_TYPE (lhs))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs))
+		       && is_pointer_compare_opcode (gimple_cond_code (s)))
+		{
+		  ptr1 = rhs;
+		  ptr2 = rhs;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	    }
+
+	  if (sanitize_subtraction_p
+	      && is_gimple_assign (s)
+	      && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS
+	      && gimple_assign_rhs_code (s) == MINUS_EXPR)
+	    {
+	      tree rhs1 = gimple_assign_rhs1 (s);
+	      tree rhs2 = gimple_assign_rhs2 (s);
+
+	      if (TREE_CODE (rhs1) == SSA_NAME
+		  && TREE_CODE (rhs2) == SSA_NAME)
+		{
+		  gassign *def1
+		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs1));
+		  gassign *def2
+		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs2));
+
+		  if (def1 && def2
+		      && gimple_assign_rhs_class (def1) == GIMPLE_UNARY_RHS
+		      && gimple_assign_rhs_class (def2) == GIMPLE_UNARY_RHS)
+		    {
+		      if (POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (def1)))
+			  && POINTER_TYPE_P
+			  (TREE_TYPE (gimple_assign_rhs1 (def2))))
+			{
+			  ptr1 = rhs1;
+			  ptr2 = rhs2;
+			  fn = BUILT_IN_ASAN_POINTER_SUBTRACT;
+			}
+		    }
+		}
+	    }
+
+	  if (ptr1 != NULL_TREE && ptr2 != NULL_TREE)
+	    {
+	      tree decl = builtin_decl_implicit (fn);
+	      gimple *g = gimple_build_call (decl, 2, ptr1, ptr2);
+	      gimple_set_location (g, gimple_location (s));
+	      gsi_insert_before (&i, g, GSI_SAME_STMT);
+	    }
+	}
+    }
+}
+
 /* Walk each instruction of all basic block and instrument those that
    represent memory references: loads, stores, or function calls.
    In a given basic block, this function avoids instrumenting memory
@@ -3432,6 +3548,9 @@ asan_instrument (void)
 {
   if (shadow_ptr_types[0] == NULL_TREE)
     asan_init_shadow_ptr_types ();
+
+  if (sanitize_flags_p (SANITIZE_POINTER_COMPARE | SANITIZE_POINTER_SUBTRACT))
+    instrument_pointer_comparison ();
   transform_statements ();
   last_alloca_addr = NULL_TREE;
   return 0;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 9ad1fb339ba..e49aef5f6ba 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10955,6 +10955,24 @@ Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 1f439d35b07..74464651e00 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_VPTR = 1UL << 22,
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
+  SANITIZE_POINTER_COMPARE = 1UL << 25,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 26,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/opts.c b/gcc/opts.c
index 5aa5d066dbe..8dee813b369 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -952,6 +952,12 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
   if (opts->x_dwarf_split_debug_info)
     opts->x_debug_generate_pub_sections = 2;
 
+  if ((opts->x_flag_sanitize
+       & (SANITIZE_POINTER_COMPARE | SANITIZE_POINTER_SUBTRACT))
+      && (opts->x_flag_sanitize
+	  & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
+    opts->x_flag_sanitize |= SANITIZE_USER_ADDRESS;
+
   /* Userspace and kernel ASan conflict with each other.  */
   if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
       && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
@@ -1496,6 +1502,10 @@ const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, (SANITIZE_POINTER_COMPARE | SANITIZE_ADDRESS),
+		 true),
+  SANITIZER_OPT (pointer-subtract, (SANITIZE_POINTER_SUBTRACT
+				    | SANITIZE_ADDRESS), true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 9d963f05c21..d06f68ba66e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..8b842c5567e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
@@ -0,0 +1,40 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p, char *q)
+{
+  return p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..0e0d3589a30
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = foo(p);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[1]);
+  bar(&global[1], &global[2]);
+  bar(&global[2], &global[1]);
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..10792264f2e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int foo(char *p, char *q)
+{
+  return p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
+
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..17cf0e6711d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
@@ -0,0 +1,32 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = bar(p, p - 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/libsanitizer/asan/asan_descriptions.cc b/libsanitizer/asan/asan_descriptions.cc
index 35d1619f2d9..eaa04378c2c 100644
--- a/libsanitizer/asan/asan_descriptions.cc
+++ b/libsanitizer/asan/asan_descriptions.cc
@@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
   return true;
 }
 
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
+{
+  AsanThread *t = FindThreadByStackAddress(addr);
+  if (!t) return false;
+
+  *shadow_addr = t->GetStackFrameVariableBeginning (addr);
+  return true;
+}
+
 static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
                                           uptr access_size, uptr prev_var_end,
                                           uptr next_var_beg) {
diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
index 584b9ba6491..b7f23b1a71b 100644
--- a/libsanitizer/asan/asan_descriptions.h
+++ b/libsanitizer/asan/asan_descriptions.h
@@ -138,6 +138,7 @@ struct StackAddressDescription {
 
 bool GetStackAddressInformation(uptr addr, uptr access_size,
                                 StackAddressDescription *descr);
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
 
 struct GlobalAddressDescription {
   uptr addr;
diff --git a/libsanitizer/asan/asan_globals.cc b/libsanitizer/asan/asan_globals.cc
index f2292926e6a..9cf195adbfa 100644
--- a/libsanitizer/asan/asan_globals.cc
+++ b/libsanitizer/asan/asan_globals.cc
@@ -122,6 +122,35 @@ int GetGlobalsForAddress(uptr addr, Global *globals, u32 *reg_sites,
   return res;
 }
 
+bool AreGlobalVariablesSameFast(uptr addr1, uptr addr2) {
+  if (addr1 > addr2) {
+    uptr tmp = addr1;
+    addr1 = addr2;
+    addr2 = tmp;
+  }
+
+  uptr aligned_addr1 = addr1 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  uptr aligned_addr2 = addr2 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+
+  u8 *shadow_ptr1 = (u8*)MemToShadow(aligned_addr1);
+  u8 *shadow_ptr2 = (u8*)MemToShadow(aligned_addr2);
+
+  // Skip fast all zero blocks.
+  unsigned int * __attribute__((may_alias)) fast1 = (unsigned int *)shadow_ptr1;
+  unsigned int * __attribute__((may_alias)) fast2 = (unsigned int *)shadow_ptr2;
+  while ((fast1 + 1) <= fast2 && *fast1 == 0)
+    fast1++;
+
+  // Check byte by byte
+  shadow_ptr1 = (u8 *)fast1;
+  while (shadow_ptr1 <= shadow_ptr2
+	 && *shadow_ptr1 != kAsanGlobalRedzoneMagic) {
+    shadow_ptr1++;
+  }
+
+  return shadow_ptr1 == shadow_ptr2;
+}
+
 enum GlobalSymbolState {
   UNREGISTERED = 0,
   REGISTERED = 1
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 84d67646b40..bcfad40bc3a 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -344,14 +344,52 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
-    GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+
+  if (a1 == a2)
+    return;
+
+  uptr shadow_offset1, shadow_offset2;
+  bool valid1, valid2;
+  {
+    ThreadRegistryLock l(&asanThreadRegistry());
+
+    valid1 = GetStackVariableBeginning(a1, &shadow_offset1);
+    valid2 = GetStackVariableBeginning(a2, &shadow_offset2);
+  }
+
+  if (valid1 && valid2) {
+    if (shadow_offset1 == shadow_offset2)
+      return;
   }
+  else if (!valid1 && !valid2) {
+    AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
+    AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
+    valid1 = chunk1.IsAllocated();
+    valid2 = chunk2.IsAllocated();
+
+    if (valid1 && valid2) {
+      if (chunk1.Eq(chunk2))
+	return;
+    }
+    else if (!valid1 && !valid2) {
+      uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
+      if (offset <= 2048) {
+	if (AreGlobalVariablesSameFast (a1, a2))
+	  return;
+      }
+
+      GlobalAddressDescription gdesc1, gdesc2;
+      valid1 = GetGlobalAddressInformation(a1, 1, &gdesc1);
+      valid2 = GetGlobalAddressInformation(a2, 1, &gdesc2);
+
+      if (valid1 && valid2
+	  && gdesc1.globals[0].beg == gdesc2.globals[0].beg)
+	return;
+    }
+  }
+
+  GET_CALLER_PC_BP_SP;
+  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
 
diff --git a/libsanitizer/asan/asan_report.h b/libsanitizer/asan/asan_report.h
index 111b8400153..8b0a8275935 100644
--- a/libsanitizer/asan/asan_report.h
+++ b/libsanitizer/asan/asan_report.h
@@ -27,6 +27,7 @@ struct StackVarDescr {
 // them to "globals" array.
 int GetGlobalsForAddress(uptr addr, __asan_global *globals, u32 *reg_sites,
                          int max_globals);
+bool AreGlobalVariablesSameFast(uptr addr1, uptr addr2);
 
 const char *MaybeDemangleGlobalName(const char *name);
 void PrintGlobalNameIfASCII(InternalScopedString *str, const __asan_global &g);
diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
index 818e1261400..d6bd051a493 100644
--- a/libsanitizer/asan/asan_thread.cc
+++ b/libsanitizer/asan/asan_thread.cc
@@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
   return true;
 }
 
+uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
+{
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  }
+  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom &&
+         (*shadow_ptr != kAsanStackLeftRedzoneMagic
+	  && *shadow_ptr != kAsanStackMidRedzoneMagic
+	  && *shadow_ptr != kAsanStackRightRedzoneMagic)) {
+    shadow_ptr--;
+  }
+
+  return (uptr)shadow_ptr;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
index c51a58ad0bb..c5adecacad4 100644
--- a/libsanitizer/asan/asan_thread.h
+++ b/libsanitizer/asan/asan_thread.h
@@ -80,6 +80,7 @@ class AsanThread {
     const char *frame_descr;
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
+  uptr GetStackFrameVariableBeginning(uptr addr);
 
   bool AddrIsInStack(uptr addr);
Jakub Jelinek Oct. 11, 2017, 2:22 p.m. UTC | #5
On Wed, Oct 11, 2017 at 03:36:40PM +0200, Martin Liška wrote:
> > std::swap(addr1, addr2); ?  I don't see it used in any of libsanitizer
> > though, so not sure if the corresponding STL header is included.
> 
> They don't use it anywhere and I had some #include issues. That's why I did it manually.

Ok.

> > There are many kinds of shadow memory markings.  My thought was that it
> > would start with a quick check, perhaps vectorized by hand (depending on if
> > the arch has unaligned loads maybe without or with a short loop for
> 
> Did that, but I have no experience how to make decision about prologue that will
> align the pointer? Any examples?

First of all, why are you aligning anything?
> +  uptr aligned_addr1 = addr1 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
> +  uptr aligned_addr2 = addr2 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
You want to compare what the pointers point to, not what the aligned pointer
points to.

Actually, looking around, there already is __sanitizer::mem_is_zero
which does what we want.

Or even __asan_region_is_poisoned(addr1, addr2 - addr1).

	Jakub
Martin Liška Oct. 12, 2017, 11:13 a.m. UTC | #6
On 10/11/2017 04:22 PM, Jakub Jelinek wrote:
> On Wed, Oct 11, 2017 at 03:36:40PM +0200, Martin Liška wrote:
>>> std::swap(addr1, addr2); ?  I don't see it used in any of libsanitizer
>>> though, so not sure if the corresponding STL header is included.
>>
>> They don't use it anywhere and I had some #include issues. That's why I did it manually.
> 
> Ok.
> 
>>> There are many kinds of shadow memory markings.  My thought was that it
>>> would start with a quick check, perhaps vectorized by hand (depending on if
>>> the arch has unaligned loads maybe without or with a short loop for
>>
>> Did that, but I have no experience how to make decision about prologue that will
>> align the pointer? Any examples?
> 
> First of all, why are you aligning anything?
>> +  uptr aligned_addr1 = addr1 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
>> +  uptr aligned_addr2 = addr2 & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
> You want to compare what the pointers point to, not what the aligned pointer
> points to.
> 
> Actually, looking around, there already is __sanitizer::mem_is_zero
> which does what we want.
> 
> Or even __asan_region_is_poisoned(addr1, addr2 - addr1).

Hi.

Thanks, the function works fine. I added various tests for global variables and found
that my check with GetGlobalAddressInformation was wrong Thus I'm adding
bool GlobalAddressDescription::PointsInsideASameVariable.

Another change I did is:
gcc -fsanitize=pointer-compare /tmp/main.c
cc1: error: ‘-fsanitize=pointer-compare’ must be combined with ‘-fsanitize=address’ or ‘-fsanitize=kernel-address’

Which would make life easier in gcc.c, where one would have to distinguish between -fsanitize=pointer-compare and
fsanitize=pointer-compare,kernel-address and according to -lasan will be added. Would it be possible to require
it explicitly?

I'm adding some numbers for postgres:

      1 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/utils/mmgr/freepage.c:673 in FreePageBtreeCleanup
      1 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/port/qsort_arg.c:168 in qsort_arg
      2 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/regex/rege_dfa.c:316 in matchuntil
      3 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/utils/mmgr/freepage.c:1543 in FreePageManagerPutInternal
      4 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/access/gin/gindatapage.c:155 in GinDataLeafPageGetItems
      4 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/common/base64.c:160 in pg_b64_decode
      4 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/common/keywords.c:103 in ScanKeywordLookup
     14 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/access/hash/hashovfl.c:768 in _hash_initbitmapbuffer
     18 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/utils/mmgr/freepage.c:187 in FreePageManagerInitialize
     26 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/port/qsort.c:188 in pg_qsort
     40 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/utils/mmgr/dsa.c:1358 in init_span
     43 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/utils/mmgr/freepage.c:1388 in FreePageManagerGetInternal
     87 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/pl/plpgsql/src/pl_scanner.c:666 in plpgsql_location_to_lineno
     92 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/port/qsort.c:168 in pg_qsort
    154 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/storage/buffer/bufmgr.c:385 in ForgetPrivateRefCountEntry
    273 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/storage/ipc/shm_toc.c:182 in shm_toc_insert
   1158 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/regex/rege_dfa.c:153 in longest
   3906 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/nodes/tidbitmap.c:840 in tbm_prepare_shared_iterate
   6545 SUMMARY: AddressSanitizer: invalid-pointer-pair /home/marxin/Programming/postgres/src/backend/utils/adt/formatting.c:4582 in NUM_numpart_to_char

There are some comparisons with NULL:
==28245==ERROR: AddressSanitizer: invalid-pointer-pair: 0x625000e42139 0x000000000000

and quite some:
Address 0x1465a178e5e0 is a wild pointer.

Which is quite interesting reason, I'll investigate where a memory comes from.

Thanks,
Martin

> 
> 	Jakub
>
From c83ea69668cc6c153024d648fb8ca565f8f16025 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 5 Oct 2017 12:14:25 +0200
Subject: [PATCH] Add -fsanitize=pointer-{compare,subtract}.

gcc/ChangeLog:

2017-10-06  Martin Liska  <mliska@suse.cz>

	* asan.c (is_pointer_compare_opcode): New function.
	(instrument_pointer_comparison): Likewise.
	(asan_instrument): Handle SANITIZE_POINTER_COMPARE and
	SANITIZE_POINTER_SUBTRACT.
	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE):
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.
	* ipa-inline.c (sanitize_attrs_match_for_inline_p): Add handling
	of SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.

gcc/testsuite/ChangeLog:

2017-10-06  Martin Liska  <mliska@suse.cz>

	* gcc.dg/asan/pointer-compare-1.c: New test.
	* gcc.dg/asan/pointer-compare-2.c: New test.
	* gcc.dg/asan/pointer-subtract-1.c: New test.
	* gcc.dg/asan/pointer-subtract-2.c: New test.
---
 gcc/asan.c                                     | 119 +++++++++++++++++++++++++
 gcc/doc/invoke.texi                            |  22 +++++
 gcc/flag-types.h                               |   2 +
 gcc/ipa-inline.c                               |   8 +-
 gcc/opts.c                                     |  15 ++++
 gcc/sanitizer.def                              |   4 +
 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c  |  64 +++++++++++++
 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c  |  47 ++++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c |  41 +++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c |  32 +++++++
 libsanitizer/asan/asan_descriptions.cc         |  27 ++++++
 libsanitizer/asan/asan_descriptions.h          |   5 ++
 libsanitizer/asan/asan_report.cc               |  58 ++++++++++--
 libsanitizer/asan/asan_thread.cc               |  23 +++++
 libsanitizer/asan/asan_thread.h                |   3 +
 15 files changed, 461 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c

diff --git a/gcc/asan.c b/gcc/asan.c
index 2aa0a795af2..6bd437e0228 100644
--- a/gcc/asan.c
+++ b/gcc/asan.c
@@ -2370,6 +2370,122 @@ maybe_instrument_call (gimple_stmt_iterator *iter)
   return instrumented;
 }
 
+/* Return true if a given opcode CODE is potentially a non-valid comparison
+   of pointer types.  */
+
+static bool
+is_pointer_compare_opcode (tree_code code)
+{
+  return (code == LE_EXPR || code == LT_EXPR || code == GE_EXPR
+	  || code == GT_EXPR);
+}
+
+/* Instrument potential invalid operation executed on pointer types:
+   comparison different from != and == and subtraction of pointers.  */
+
+static void
+instrument_pointer_comparison (void)
+{
+  basic_block bb;
+  gimple_stmt_iterator i;
+
+  bool sanitize_comparison_p = sanitize_flags_p (SANITIZE_POINTER_COMPARE);
+  bool sanitize_subtraction_p = sanitize_flags_p (SANITIZE_POINTER_SUBTRACT);
+
+  FOR_EACH_BB_FN (bb, cfun)
+    {
+      for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
+	{
+	  gimple *s = gsi_stmt (i);
+
+	  tree ptr1 = NULL_TREE;
+	  tree ptr2 = NULL_TREE;
+	  enum built_in_function fn = BUILT_IN_NONE;
+
+	  if (sanitize_comparison_p)
+	    {
+	      tree cond_expr, rhs1, rhs2, lhs, rhs;
+
+	      if (is_gimple_assign (s)
+		  && is_pointer_compare_opcode (gimple_assign_rhs_code (s))
+		  && (rhs1 = gimple_assign_rhs1 (s))
+		  && (rhs2 = gimple_assign_rhs2 (s))
+		  && POINTER_TYPE_P (TREE_TYPE (rhs1))
+		  && POINTER_TYPE_P (TREE_TYPE (rhs2)))
+		{
+		  ptr1 = rhs1;
+		  ptr2 = rhs2;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	      else if (is_gimple_assign (s)
+		       && gimple_assign_rhs_code (s) == COND_EXPR
+		       && (cond_expr = gimple_assign_rhs1 (s))
+		       && is_pointer_compare_opcode (TREE_CODE (cond_expr))
+		       && (rhs1 = TREE_OPERAND (cond_expr, 0))
+		       && (rhs2 = TREE_OPERAND (cond_expr, 1))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs1))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs2)))
+		{
+		  ptr1 = rhs1;
+		  ptr2 = rhs2;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	      else if (gimple_code (s) == GIMPLE_COND
+		       && (lhs = gimple_cond_lhs (s))
+		       && (rhs = gimple_cond_rhs (s))
+		       && POINTER_TYPE_P (TREE_TYPE (lhs))
+		       && POINTER_TYPE_P (TREE_TYPE (rhs))
+		       && is_pointer_compare_opcode (gimple_cond_code (s)))
+		{
+		  ptr1 = rhs;
+		  ptr2 = rhs;
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	    }
+
+	  if (sanitize_subtraction_p
+	      && is_gimple_assign (s)
+	      && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS
+	      && gimple_assign_rhs_code (s) == MINUS_EXPR)
+	    {
+	      tree rhs1 = gimple_assign_rhs1 (s);
+	      tree rhs2 = gimple_assign_rhs2 (s);
+
+	      if (TREE_CODE (rhs1) == SSA_NAME
+		  && TREE_CODE (rhs2) == SSA_NAME)
+		{
+		  gassign *def1
+		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs1));
+		  gassign *def2
+		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs2));
+
+		  if (def1 && def2
+		      && gimple_assign_rhs_class (def1) == GIMPLE_UNARY_RHS
+		      && gimple_assign_rhs_class (def2) == GIMPLE_UNARY_RHS)
+		    {
+		      if (POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (def1)))
+			  && POINTER_TYPE_P
+			  (TREE_TYPE (gimple_assign_rhs1 (def2))))
+			{
+			  ptr1 = rhs1;
+			  ptr2 = rhs2;
+			  fn = BUILT_IN_ASAN_POINTER_SUBTRACT;
+			}
+		    }
+		}
+	    }
+
+	  if (ptr1 != NULL_TREE && ptr2 != NULL_TREE)
+	    {
+	      tree decl = builtin_decl_implicit (fn);
+	      gimple *g = gimple_build_call (decl, 2, ptr1, ptr2);
+	      gimple_set_location (g, gimple_location (s));
+	      gsi_insert_before (&i, g, GSI_SAME_STMT);
+	    }
+	}
+    }
+}
+
 /* Walk each instruction of all basic block and instrument those that
    represent memory references: loads, stores, or function calls.
    In a given basic block, this function avoids instrumenting memory
@@ -3432,6 +3548,9 @@ asan_instrument (void)
 {
   if (shadow_ptr_types[0] == NULL_TREE)
     asan_init_shadow_ptr_types ();
+
+  if (sanitize_flags_p (SANITIZE_POINTER_COMPARE | SANITIZE_POINTER_SUBTRACT))
+    instrument_pointer_comparison ();
   transform_statements ();
   last_alloca_addr = NULL_TREE;
   return 0;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 9ad1fb339ba..669adeb38b6 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10955,6 +10955,28 @@ Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 1f439d35b07..74464651e00 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_VPTR = 1UL << 22,
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
+  SANITIZE_POINTER_COMPARE = 1UL << 25,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 26,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index dd46cb61362..d60432cded3 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -263,8 +263,12 @@ sanitize_attrs_match_for_inline_p (const_tree caller, const_tree callee)
   if (!caller || !callee)
     return true;
 
-  return sanitize_flags_p (SANITIZE_ADDRESS, caller)
-    == sanitize_flags_p (SANITIZE_ADDRESS, callee);
+  return ((sanitize_flags_p (SANITIZE_ADDRESS, caller)
+	   == sanitize_flags_p (SANITIZE_ADDRESS, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_COMPARE, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_COMPARE, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, callee)));
 }
 
 /* Used for flags where it is safe to inline when caller's value is
diff --git a/gcc/opts.c b/gcc/opts.c
index 5aa5d066dbe..36aa7056195 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -952,6 +952,19 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
   if (opts->x_dwarf_split_debug_info)
     opts->x_debug_generate_pub_sections = 2;
 
+  if ((opts->x_flag_sanitize
+       & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
+    {
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE)
+	error_at (loc,
+		  "%<-fsanitize=pointer-compare%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
+	error_at (loc,
+		  "%<-fsanitize=pointer-subtract%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+    }
+
   /* Userspace and kernel ASan conflict with each other.  */
   if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
       && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
@@ -1496,6 +1509,8 @@ const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
+  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 9d963f05c21..d06f68ba66e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..854f6853193
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
@@ -0,0 +1,64 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p, char *q)
+{
+  return p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+char small_global[7] = {};
+char large_global[5000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  char *p = &small_global[0];
+  foo (p, p); // OK
+  foo (p, p + 7); // OK
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p + 8);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p + 8);
+
+  p = &large_global[0];
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &small_global[0]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..87ff8bd7fbb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
@@ -0,0 +1,47 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+char small_global[7] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = foo(p);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[1]);
+  bar(&global[1], &global[2]);
+  bar(&global[2], &global[1]);
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+  bar(&small_global[0], &small_global[1]);
+  bar(&small_global[0], &small_global[7]);
+  bar(&small_global[7], &small_global[1]);
+  bar(&small_global[6], &small_global[7]);
+  bar(&small_global[7], &small_global[7]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..10792264f2e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int foo(char *p, char *q)
+{
+  return p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
+
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..17cf0e6711d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
@@ -0,0 +1,32 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = bar(p, p - 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/libsanitizer/asan/asan_descriptions.cc b/libsanitizer/asan/asan_descriptions.cc
index 35d1619f2d9..004cd6aca18 100644
--- a/libsanitizer/asan/asan_descriptions.cc
+++ b/libsanitizer/asan/asan_descriptions.cc
@@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
   return true;
 }
 
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
+{
+  AsanThread *t = FindThreadByStackAddress(addr);
+  if (!t) return false;
+
+  *shadow_addr = t->GetStackFrameVariableBeginning (addr);
+  return true;
+}
+
 static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
                                           uptr access_size, uptr prev_var_end,
                                           uptr next_var_beg) {
@@ -330,6 +339,24 @@ void GlobalAddressDescription::Print(const char *bug_type) const {
   }
 }
 
+bool GlobalAddressDescription::PointsInsideASameVariable
+  (const GlobalAddressDescription &other) const
+{
+  // we must have just a single variables candidate
+  if (size != 1 || other.size != 1)
+    return false;
+
+  if (globals[0].beg != other.globals[0].beg)
+    return false;
+
+  // check that both descriptions are inside a same global variable
+  return (globals[0].beg <= addr
+      && other.globals[0].beg <= other.addr
+      && (addr + access_size) <= (globals[0].beg + globals[0].size)
+      && ((other.addr + other.access_size)
+	<= (other.globals[0].beg + other.globals[0].size)));
+}
+
 void StackAddressDescription::Print() const {
   Decorator d;
   char tname[128];
diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
index 584b9ba6491..35baec6f2f9 100644
--- a/libsanitizer/asan/asan_descriptions.h
+++ b/libsanitizer/asan/asan_descriptions.h
@@ -138,6 +138,7 @@ struct StackAddressDescription {
 
 bool GetStackAddressInformation(uptr addr, uptr access_size,
                                 StackAddressDescription *descr);
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
 
 struct GlobalAddressDescription {
   uptr addr;
@@ -149,6 +150,10 @@ struct GlobalAddressDescription {
   u8 size;
 
   void Print(const char *bug_type = "") const;
+
+  // Return true when this descriptions points inside a same global variable
+  // as other. Descriptions can have different address within the variable
+  bool PointsInsideASameVariable (const GlobalAddressDescription &other) const;
 };
 
 bool GetGlobalAddressInformation(uptr addr, uptr access_size,
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 84d67646b40..bf10e6944f1 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -344,14 +344,58 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
-    GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+
+  if (a1 == a2)
+    return;
+
+  uptr shadow_offset1, shadow_offset2;
+  bool valid1, valid2;
+  {
+    ThreadRegistryLock l(&asanThreadRegistry());
+
+    valid1 = GetStackVariableBeginning(a1, &shadow_offset1);
+    valid2 = GetStackVariableBeginning(a2, &shadow_offset2);
+  }
+
+  if (valid1 && valid2) {
+    if (shadow_offset1 == shadow_offset2)
+      return;
   }
+  else if (!valid1 && !valid2) {
+    AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
+    AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
+    valid1 = chunk1.IsAllocated();
+    valid2 = chunk2.IsAllocated();
+
+    if (valid1 && valid2) {
+      if (chunk1.Eq(chunk2))
+	return;
+    }
+    else if (!valid1 && !valid2) {
+      uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
+      uptr left = a1 < a2 ? a1 : a2;
+      if (offset <= 2048) {
+	if (__asan_region_is_poisoned (left, offset) == 0)
+	  return;
+	else {
+	  GET_CALLER_PC_BP_SP;
+	  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+	  return;
+	}
+      }
+
+      GlobalAddressDescription gdesc1, gdesc2;
+      valid1 = GetGlobalAddressInformation(a1, 0, &gdesc1);
+      valid2 = GetGlobalAddressInformation(a2, 0, &gdesc2);
+
+      if (valid1 && valid2
+	  && gdesc1.PointsInsideASameVariable (gdesc2))
+	return;
+    }
+  }
+
+  GET_CALLER_PC_BP_SP;
+  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
 
diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
index 818e1261400..0ffca7af35e 100644
--- a/libsanitizer/asan/asan_thread.cc
+++ b/libsanitizer/asan/asan_thread.cc
@@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
   return true;
 }
 
+uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
+{
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  }
+  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom
+	  && (*shadow_ptr != kAsanStackLeftRedzoneMagic
+	  && *shadow_ptr != kAsanStackMidRedzoneMagic
+	  && *shadow_ptr != kAsanStackRightRedzoneMagic)) {
+    shadow_ptr--;
+  }
+
+  return (uptr)shadow_ptr;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
index c51a58ad0bb..edd1d815632 100644
--- a/libsanitizer/asan/asan_thread.h
+++ b/libsanitizer/asan/asan_thread.h
@@ -81,6 +81,9 @@ class AsanThread {
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
 
+  // Return beginning of a stack variable in shadow memory
+  uptr GetStackFrameVariableBeginning(uptr addr);
+
   bool AddrIsInStack(uptr addr);
 
   void DeleteFakeStack(int tid) {
Martin Liška Oct. 12, 2017, 11:30 a.m. UTC | #7
Hi.

There's one false positive I've noticed:

$ cat /tmp/ptr-cmp.c
int
__attribute__((noinline))
foo(char *p1, char *p2)
{
  if (p2 != 0 && p1 > p2)
    return 0;

  return 1;
}

int main(int argc, char **argv)
{
  return foo(argv[0], 0);
}

$  gcc /tmp/ptr-cmp.c -fsanitize=address,pointer-compare -O2 -fdump-tree-asan1=/dev/stdout && ./a.out

__attribute__((noinline))
foo (char * p1, char * p2)
{
  _Bool _1;
  _Bool _2;
  _Bool _3;
  _Bool _8;
  int _9;

  <bb 2> [100.00%] [count: INV]:
  _1 = p2_5(D) != 0B;
  __builtin___sanitizer_ptr_cmp (p2_5(D), p1_6(D));
  _2 = p2_5(D) < p1_6(D);
  _3 = _1 & _2;
  _8 = ~_3;
  _9 = (int) _8;
  return _9;

}

==31859==ERROR: AddressSanitizer: invalid-pointer-pair: 0x000000000000 0x7ffccadb4ff9
    #0 0x400756 in foo (/home/marxin/Programming/postgres/src/pl/plpgsql/src/a.out+0x400756)
    #1 0x1513cde71f49 in __libc_start_main (/lib64/libc.so.6+0x20f49)
    #2 0x400689 in _start (/home/marxin/Programming/postgres/src/pl/plpgsql/src/a.out+0x400689)

As I've been reading dump files, it's already in gimple dump:
cat ptr-cmp.c.004t.gimple
__attribute__((noinline))
foo (char * p1, char * p2)
{
  int D.2181;

  _1 = p2 != 0B;
  _2 = p1 > p2;
  _3 = _1 & _2;
  if (_3 != 0) goto <D.2179>; else goto <D.2180>;
...

Thoughts?
Martin
Jakub Jelinek Oct. 12, 2017, 11:34 a.m. UTC | #8
On Thu, Oct 12, 2017 at 01:13:56PM +0200, Martin Liška wrote:
> +  if (a1 == a2)
> +    return;
> +
> +  uptr shadow_offset1, shadow_offset2;
> +  bool valid1, valid2;
> +  {
> +    ThreadRegistryLock l(&asanThreadRegistry());
> +
> +    valid1 = GetStackVariableBeginning(a1, &shadow_offset1);
> +    valid2 = GetStackVariableBeginning(a2, &shadow_offset2);
> +  }
> +
> +  if (valid1 && valid2) {
> +    if (shadow_offset1 == shadow_offset2)
> +      return;
>    }
> +  else if (!valid1 && !valid2) {
> +    AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
> +    AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
> +    valid1 = chunk1.IsAllocated();
> +    valid2 = chunk2.IsAllocated();
> +
> +    if (valid1 && valid2) {
> +      if (chunk1.Eq(chunk2))
> +	return;
> +    }
> +    else if (!valid1 && !valid2) {
> +      uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
> +      uptr left = a1 < a2 ? a1 : a2;
> +      if (offset <= 2048) {
> +	if (__asan_region_is_poisoned (left, offset) == 0)
> +	  return;
> +	else {
> +	  GET_CALLER_PC_BP_SP;
> +	  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
> +	  return;
> +	}

My assumption was that this offset/left stuff would be done
right after the if (a1 == a2) above, i.e. after the most common
case - equality comparison, handle the case of pointers close to each other
without any expensive locking and data structure lookup (also, you only need
to compute left if offset is <= 2048).  Only for larger distances, you'd
go through the other cases.  I.e. see if one or both pointers point
into a stack variable, or heap, or global registered variable.

For those 3 cases, I wonder if pairs of valid1 = ...; valid2 = ...;
is what is most efficient.  What we really want is to error out if
one pointer is valid in any of the categories, but the other is
not.  So, isn't the best general algorithm something like:
  if (a1 == a2)
    return;
  if (distance <= 2048)
    {
      if (not poisoned area)
	return;
    }
  else if (heap (a1))
    {
      if (heap (a2) && same_heap_object)
	return;
    }
  else if (stackvar (a1))
    {
      if (stackvar (a2) && same_stackvar)
	return;
    }
  else if (globalvar (a1))
    {
      if (globalvar (a2) && same_globalvar)
        return;
    }
  else
    return; /* ??? We don't know what the pointers point to, punt.
               Or perhaps try the not poisoned area check even for
               slightly larger distances (like 16K) and only punt
               for larger?  */
  error;

?  What order of the a1 tests should be done depends on how expensive
those tests are and perhaps some data gathering on real-world apps
on what pointers in the comparisons/subtracts are most common.

	Jakub
Jakub Jelinek Oct. 12, 2017, 11:38 a.m. UTC | #9
On Thu, Oct 12, 2017 at 01:30:34PM +0200, Martin Liška wrote:
> There's one false positive I've noticed:
> 
> $ cat /tmp/ptr-cmp.c
> int
> __attribute__((noinline))
> foo(char *p1, char *p2)
> {
>   if (p2 != 0 && p1 > p2)
>     return 0;
> 
>   return 1;
> }

Guess that is an argument for instrumenting pointer-compare/pointer-subtract
earlier (in the FEs, perhaps into internal-fn).

Because then it will have side-effects and thus folding (generic as well as
during gimplification and on early gimple) will not do this kind of
optimization with it.
Of course you'd need to handle constexpr and folding in initializers then...

	Jakub
Martin Liška Oct. 13, 2017, 11:01 a.m. UTC | #10
On 10/12/2017 01:34 PM, Jakub Jelinek wrote:
> On Thu, Oct 12, 2017 at 01:13:56PM +0200, Martin Liška wrote:
>> +  if (a1 == a2)
>> +    return;
>> +
>> +  uptr shadow_offset1, shadow_offset2;
>> +  bool valid1, valid2;
>> +  {
>> +    ThreadRegistryLock l(&asanThreadRegistry());
>> +
>> +    valid1 = GetStackVariableBeginning(a1, &shadow_offset1);
>> +    valid2 = GetStackVariableBeginning(a2, &shadow_offset2);
>> +  }
>> +
>> +  if (valid1 && valid2) {
>> +    if (shadow_offset1 == shadow_offset2)
>> +      return;
>>    }
>> +  else if (!valid1 && !valid2) {
>> +    AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
>> +    AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
>> +    valid1 = chunk1.IsAllocated();
>> +    valid2 = chunk2.IsAllocated();
>> +
>> +    if (valid1 && valid2) {
>> +      if (chunk1.Eq(chunk2))
>> +	return;
>> +    }
>> +    else if (!valid1 && !valid2) {
>> +      uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
>> +      uptr left = a1 < a2 ? a1 : a2;
>> +      if (offset <= 2048) {
>> +	if (__asan_region_is_poisoned (left, offset) == 0)
>> +	  return;
>> +	else {
>> +	  GET_CALLER_PC_BP_SP;
>> +	  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
>> +	  return;
>> +	}
> 
> My assumption was that this offset/left stuff would be done
> right after the if (a1 == a2) above, i.e. after the most common
> case - equality comparison, handle the case of pointers close to each other
> without any expensive locking and data structure lookup (also, you only need
> to compute left if offset is <= 2048).  Only for larger distances, you'd
> go through the other cases.  I.e. see if one or both pointers point
> into a stack variable, or heap, or global registered variable.
> 
> For those 3 cases, I wonder if pairs of valid1 = ...; valid2 = ...;
> is what is most efficient.  What we really want is to error out if
> one pointer is valid in any of the categories, but the other is
> not.  So, isn't the best general algorithm something like:
>   if (a1 == a2)
>     return;
>   if (distance <= 2048)
>     {
>       if (not poisoned area)
> 	return;
>     }
>   else if (heap (a1))
>     {
>       if (heap (a2) && same_heap_object)
> 	return;
>     }
>   else if (stackvar (a1))
>     {
>       if (stackvar (a2) && same_stackvar)
> 	return;
>     }
>   else if (globalvar (a1))
>     {
>       if (globalvar (a2) && same_globalvar)
>         return;
>     }
>   else
>     return; /* ??? We don't know what the pointers point to, punt.
>                Or perhaps try the not poisoned area check even for
>                slightly larger distances (like 16K) and only punt
>                for larger?  */
>   error;
> 
> ?  What order of the a1 tests should be done depends on how expensive
> those tests are and perhaps some data gathering on real-world apps
> on what pointers in the comparisons/subtracts are most common.
> 
> 	Jakub
> 

Thanks Jakub for valuable feedback. I'm sending new version where I implemented what you pointed.
I also moved builtin created to FE and fixed quite some bugs I seen on postgres database.

I guess I should slowly start with review process of libsanitizer changes. They are quite some.

Martin
From 28401b05e8da21e57c8c0388fcc71fcbf34e0002 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 5 Oct 2017 12:14:25 +0200
Subject: [PATCH] Add -fsanitize=pointer-{compare,subtract}.

gcc/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* ipa-inline.c (sanitize_attrs_match_for_inline_p): Add handling
	of SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE): Likewise.
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.

gcc/c/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* c-typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(build_binary_op): Similar for pointer comparison.

gcc/testsuite/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* gcc.dg/asan/pointer-compare-1.c: New test.
	* gcc.dg/asan/pointer-compare-2.c: New test.
	* gcc.dg/asan/pointer-subtract-1.c: New test.
	* gcc.dg/asan/pointer-subtract-2.c: New test.
---
 gcc/c/c-typeck.c                               | 31 +++++++++--
 gcc/doc/invoke.texi                            | 22 ++++++++
 gcc/flag-types.h                               |  2 +
 gcc/ipa-inline.c                               |  8 ++-
 gcc/opts.c                                     | 15 +++++
 gcc/sanitizer.def                              |  4 ++
 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c  | 77 ++++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c  | 72 ++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c | 41 ++++++++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c | 32 +++++++++++
 libsanitizer/asan/asan_descriptions.cc         | 29 ++++++++++
 libsanitizer/asan/asan_descriptions.h          |  5 ++
 libsanitizer/asan/asan_report.cc               | 68 ++++++++++++++++++++---
 libsanitizer/asan/asan_thread.cc               | 23 ++++++++
 libsanitizer/asan/asan_thread.h                |  3 +
 15 files changed, 419 insertions(+), 13 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c

diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index cb9c589e061..e4dc2833c91 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -96,7 +96,7 @@ static tree lookup_field (tree, tree);
 static int convert_arguments (location_t, vec<location_t>, tree,
 			      vec<tree, va_gc> *, vec<tree, va_gc> *, tree,
 			      tree);
-static tree pointer_diff (location_t, tree, tree);
+static tree pointer_diff (location_t, tree, tree, tree *);
 static tree convert_for_assignment (location_t, location_t, tree, tree, tree,
 				    enum impl_conv, bool, tree, tree, int);
 static tree valid_compound_expr_initializer (tree, tree);
@@ -3779,10 +3779,11 @@ parser_build_binary_op (location_t location, enum tree_code code,
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
-pointer_diff (location_t loc, tree op0, tree op1)
+pointer_diff (location_t loc, tree op0, tree op1, tree *instrument_expr)
 {
   tree restype = ptrdiff_type_node;
   tree result, inttype;
@@ -3826,6 +3827,18 @@ pointer_diff (location_t loc, tree op0, tree op1)
     pedwarn (loc, OPT_Wpointer_arith,
 	     "pointer to a function used in subtraction");
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      gcc_assert (current_function_decl != NULL_TREE);
+
+      op0 = c_fully_fold (op0, false, NULL);
+      op1 = c_fully_fold (op1, false, NULL);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr
+	= build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction as integers;
      then drop through to build the divide operator.
      Do not do default conversions on the minus operator
@@ -11188,7 +11201,7 @@ build_binary_op (location_t location, enum tree_code code,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && comp_target_types (location, type0, type1))
 	{
-	  ret = pointer_diff (location, op0, op1);
+	  ret = pointer_diff (location, op0, op1, &instrument_expr);
 	  goto return_build_binary_op;
 	}
       /* Handle pointer minus int.  Just like pointer plus int.  */
@@ -11707,6 +11720,16 @@ build_binary_op (location_t location, enum tree_code code,
 	      pedwarn (location, 0,
 		       "comparison of distinct pointer types lacks a cast");
 	    }
+
+	  if (sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	    {
+	      gcc_assert (current_function_decl != NULL_TREE);
+
+	      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	      instrument_expr
+		= build_call_expr_loc (location, tt, 2, unshare_expr (op0),
+				       unshare_expr (op1));
+	    }
 	}
       else if (code0 == POINTER_TYPE && null_pointer_constant_p (orig_op1))
 	{
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 4e7dfb33c31..6cec1fea033 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10960,6 +10960,28 @@ Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 1f439d35b07..74464651e00 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_VPTR = 1UL << 22,
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
+  SANITIZE_POINTER_COMPARE = 1UL << 25,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 26,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index dd46cb61362..d60432cded3 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -263,8 +263,12 @@ sanitize_attrs_match_for_inline_p (const_tree caller, const_tree callee)
   if (!caller || !callee)
     return true;
 
-  return sanitize_flags_p (SANITIZE_ADDRESS, caller)
-    == sanitize_flags_p (SANITIZE_ADDRESS, callee);
+  return ((sanitize_flags_p (SANITIZE_ADDRESS, caller)
+	   == sanitize_flags_p (SANITIZE_ADDRESS, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_COMPARE, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_COMPARE, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, callee)));
 }
 
 /* Used for flags where it is safe to inline when caller's value is
diff --git a/gcc/opts.c b/gcc/opts.c
index adf3d89851d..f39ead7e1cf 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -952,6 +952,19 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
   if (opts->x_dwarf_split_debug_info)
     opts->x_debug_generate_pub_sections = 2;
 
+  if ((opts->x_flag_sanitize
+       & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
+    {
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE)
+	error_at (loc,
+		  "%<-fsanitize=pointer-compare%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
+	error_at (loc,
+		  "%<-fsanitize=pointer-subtract%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+    }
+
   /* Userspace and kernel ASan conflict with each other.  */
   if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
       && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
@@ -1496,6 +1509,8 @@ const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
+  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 9d963f05c21..d06f68ba66e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..0e448cf2a3a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
@@ -0,0 +1,77 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p, char *q)
+{
+  return p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+char small_global[7] = {};
+char large_global[5000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+  __builtin_free (heap1);
+  __builtin_free (heap2);
+
+  heap1 = (char *)__builtin_malloc(1024);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 1025);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1 + 1024, heap1 + 1025);
+  __builtin_free (heap1);
+
+  heap1 = (char *)__builtin_malloc(4096);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 4097);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  char *p = &small_global[0];
+  foo (p, p); // OK
+  foo (p, p + 7); // OK
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p + 8);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p + 8);
+
+  p = &large_global[0];
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &small_global[0]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..c4829ad2b83
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
@@ -0,0 +1,72 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+int baz(char *p, char *q)
+{
+  return p != 0 && p < q;
+}
+
+char global[8192] = {};
+char small_global[7] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = foo(p);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(1024);
+  bar(p, p + 1024);
+  bar(p + 1024, p + 1023);
+  bar(p + 1, p + 1023);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(4096);
+  bar(p, p + 4096);
+  bar(p + 10, p + 100);
+  bar(p + 1024, p + 4096);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[1]);
+  bar(&global[1], &global[2]);
+  bar(&global[2], &global[1]);
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[7000]);
+  bar(&global[500], &global[10]);
+  p = &global[0];
+  bar(p, p + 8192);
+  p = &global[8000];
+  bar(p, p + 192);
+
+  p = &small_global[0];
+  bar (p, p + 1);
+  bar (p, p + 7);
+  bar (p + 7, p + 1);
+  bar (p + 6, p + 7);
+  bar (p + 7, p + 7);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  baz(0, &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..10792264f2e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int foo(char *p, char *q)
+{
+  return p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
+
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..17cf0e6711d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
@@ -0,0 +1,32 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = bar(p, p - 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/libsanitizer/asan/asan_descriptions.cc b/libsanitizer/asan/asan_descriptions.cc
index 35d1619f2d9..934618c3181 100644
--- a/libsanitizer/asan/asan_descriptions.cc
+++ b/libsanitizer/asan/asan_descriptions.cc
@@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
   return true;
 }
 
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
+{
+  AsanThread *t = FindThreadByStackAddress(addr);
+  if (!t) return false;
+
+  *shadow_addr = t->GetStackFrameVariableBeginning (addr);
+  return true;
+}
+
 static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
                                           uptr access_size, uptr prev_var_end,
                                           uptr next_var_beg) {
@@ -330,6 +339,26 @@ void GlobalAddressDescription::Print(const char *bug_type) const {
   }
 }
 
+bool GlobalAddressDescription::PointsInsideASameVariable
+  (const GlobalAddressDescription &other) const
+{
+  if (size == 0 || other.size == 0)
+    return false;
+
+  for (unsigned i = 0; i < size; i++)
+    for (unsigned j = 0; j < other.size; j++) {
+      if (globals[i].beg == other.globals[j].beg
+	  && globals[i].beg <= addr
+	  && other.globals[j].beg <= other.addr
+	  && (addr + access_size) < (globals[i].beg + globals[i].size)
+	  && ((other.addr + other.access_size)
+	    < (other.globals[j].beg + other.globals[j].size)))
+	return true;
+    }
+
+  return false;
+}
+
 void StackAddressDescription::Print() const {
   Decorator d;
   char tname[128];
diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
index 584b9ba6491..35baec6f2f9 100644
--- a/libsanitizer/asan/asan_descriptions.h
+++ b/libsanitizer/asan/asan_descriptions.h
@@ -138,6 +138,7 @@ struct StackAddressDescription {
 
 bool GetStackAddressInformation(uptr addr, uptr access_size,
                                 StackAddressDescription *descr);
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
 
 struct GlobalAddressDescription {
   uptr addr;
@@ -149,6 +150,10 @@ struct GlobalAddressDescription {
   u8 size;
 
   void Print(const char *bug_type = "") const;
+
+  // Return true when this descriptions points inside a same global variable
+  // as other. Descriptions can have different address within the variable
+  bool PointsInsideASameVariable (const GlobalAddressDescription &other) const;
 };
 
 bool GetGlobalAddressInformation(uptr addr, uptr access_size,
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 84d67646b40..2704f3c8095 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -344,14 +344,68 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
-    GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+
+  if (a1 == a2)
+    return;
+
+  uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
+  uptr left = a1 < a2 ? a1 : a2;
+  uptr right = a1 < a2 ? a2 : a1;
+  if (offset <= 2048) {
+    if (__asan_region_is_poisoned (left, offset) == 0)
+      return;
+  }
+
+  uptr shadow_offset1, shadow_offset2;
+  bool valid1, valid2;
+  {
+    ThreadRegistryLock l(&asanThreadRegistry());
+    valid1 = GetStackVariableBeginning(left, &shadow_offset1);
   }
+
+  // check whether a1 is a stack memory pointer
+  if (valid1) {
+    {
+      ThreadRegistryLock l(&asanThreadRegistry());
+      valid2 = GetStackVariableBeginning(right - 1, &shadow_offset2);
+    }
+
+    if (valid2 && shadow_offset1 == shadow_offset2)
+      return;
+  }
+  // check whether a1 is a heap memory address
+  else {
+    HeapAddressDescription hdesc1;
+    valid1 = GetHeapAddressInformation(a1, 0, &hdesc1);
+
+    if (valid1 && hdesc1.chunk_access.access_type == kAccessTypeInside) {
+      HeapAddressDescription hdesc2;
+      valid2 = GetHeapAddressInformation(a2, 0, &hdesc2);
+
+      if (valid2
+	  && hdesc2.chunk_access.access_type == kAccessTypeInside
+	  && (hdesc1.chunk_access.chunk_begin
+	    == hdesc2.chunk_access.chunk_begin))
+	return;
+    } else {
+      // check whether a1 is an address of a global variable
+      GlobalAddressDescription gdesc1;
+      valid1 = GetGlobalAddressInformation(left, 0, &gdesc1);
+
+      if (valid1) {
+	GlobalAddressDescription gdesc2;
+	valid2 = GetGlobalAddressInformation(right - 1, 0, &gdesc2);
+
+	if (valid2 && gdesc1.PointsInsideASameVariable (gdesc2))
+	  return;
+      } else {
+	// TODO
+      }
+    }
+  }
+
+  GET_CALLER_PC_BP_SP;
+  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
 
diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
index 818e1261400..0ffca7af35e 100644
--- a/libsanitizer/asan/asan_thread.cc
+++ b/libsanitizer/asan/asan_thread.cc
@@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
   return true;
 }
 
+uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
+{
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  }
+  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom
+	  && (*shadow_ptr != kAsanStackLeftRedzoneMagic
+	  && *shadow_ptr != kAsanStackMidRedzoneMagic
+	  && *shadow_ptr != kAsanStackRightRedzoneMagic)) {
+    shadow_ptr--;
+  }
+
+  return (uptr)shadow_ptr;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
index c51a58ad0bb..edd1d815632 100644
--- a/libsanitizer/asan/asan_thread.h
+++ b/libsanitizer/asan/asan_thread.h
@@ -81,6 +81,9 @@ class AsanThread {
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
 
+  // Return beginning of a stack variable in shadow memory
+  uptr GetStackFrameVariableBeginning(uptr addr);
+
   bool AddrIsInStack(uptr addr);
 
   void DeleteFakeStack(int tid) {
Jakub Jelinek Oct. 13, 2017, 11:17 a.m. UTC | #11
On Fri, Oct 13, 2017 at 01:01:37PM +0200, Martin Liška wrote:
> @@ -3826,6 +3827,18 @@ pointer_diff (location_t loc, tree op0, tree op1)
>      pedwarn (loc, OPT_Wpointer_arith,
>  	     "pointer to a function used in subtraction");
>  
> +  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
> +    {
> +      gcc_assert (current_function_decl != NULL_TREE);
> +
> +      op0 = c_fully_fold (op0, false, NULL);
> +      op1 = c_fully_fold (op1, false, NULL);
> +
> +      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
> +      *instrument_expr
> +	= build_call_expr_loc (loc, tt, 2, op0, op1);
> +    }

If op0 or op1 have some embedded side-effects, won't you evaluate them
multiple times?  I'd expect you need to c_save_expr them and make sure the
actual pointer difference expression uses the result of the save expr too.

> --- a/libsanitizer/asan/asan_report.cc
> +++ b/libsanitizer/asan/asan_report.cc
> @@ -344,14 +344,68 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
>    if (!flags()->detect_invalid_pointer_pairs) return;
>    uptr a1 = reinterpret_cast<uptr>(p1);
>    uptr a2 = reinterpret_cast<uptr>(p2);
> -  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
> -  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
> -  bool valid1 = chunk1.IsAllocated();
> -  bool valid2 = chunk2.IsAllocated();
> -  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
> -    GET_CALLER_PC_BP_SP;
> -    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
> +
> +  if (a1 == a2)
> +    return;
> +
> +  uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
> +  uptr left = a1 < a2 ? a1 : a2;
> +  uptr right = a1 < a2 ? a2 : a1;
> +  if (offset <= 2048) {
> +    if (__asan_region_is_poisoned (left, offset) == 0)
> +      return;
> +  }

If offset <= 2048 and something is poisoned in between, then it is
clearly a failure, so you should either goto the error or duplicate
the two lines inside of the outer if above.

> +
> +  uptr shadow_offset1, shadow_offset2;
> +  bool valid1, valid2;
> +  {
> +    ThreadRegistryLock l(&asanThreadRegistry());
> +    valid1 = GetStackVariableBeginning(left, &shadow_offset1);
>    }
> +
> +  // check whether a1 is a stack memory pointer
> +  if (valid1) {
> +    {
> +      ThreadRegistryLock l(&asanThreadRegistry());
> +      valid2 = GetStackVariableBeginning(right - 1, &shadow_offset2);

Why do you take the registery lock twice?  Just do:
  {
    ThreadRegistryLock l(&asanThreadRegistry());
    if (GetStackVariableBeginning(left, &shadow_offset1))
      {
        if (GetStackVariableBeginning(right - 1, &shadow_offset2) &&
	    shadow_offset1 == shadow_offset2)
	  return;
// goto do_error; or:
	GET_CALLER_PC_BP_SP;
	ReportInvalidPointerPair(pc, bp, sp, a1, a2);
	return;
      }
  }

> +    }
> +
> +    if (valid2 && shadow_offset1 == shadow_offset2)
> +      return;
> +  }
> +  // check whether a1 is a heap memory address
> +  else {
> +    HeapAddressDescription hdesc1;
> +    valid1 = GetHeapAddressInformation(a1, 0, &hdesc1);
> +
> +    if (valid1 && hdesc1.chunk_access.access_type == kAccessTypeInside) {
> +      HeapAddressDescription hdesc2;
> +      valid2 = GetHeapAddressInformation(a2, 0, &hdesc2);

Again, no need for valid1/valid2.  Why do you use above left and right - 1
and here a1 and a2?  Shouldn't it be always left and right - 1?

	Jakub
Martin Liška Oct. 13, 2017, 12:53 p.m. UTC | #12
On 10/13/2017 01:17 PM, Jakub Jelinek wrote:
> On Fri, Oct 13, 2017 at 01:01:37PM +0200, Martin Liška wrote:
>> @@ -3826,6 +3827,18 @@ pointer_diff (location_t loc, tree op0, tree op1)
>>      pedwarn (loc, OPT_Wpointer_arith,
>>  	     "pointer to a function used in subtraction");
>>  
>> +  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
>> +    {
>> +      gcc_assert (current_function_decl != NULL_TREE);
>> +
>> +      op0 = c_fully_fold (op0, false, NULL);
>> +      op1 = c_fully_fold (op1, false, NULL);
>> +
>> +      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
>> +      *instrument_expr
>> +	= build_call_expr_loc (loc, tt, 2, op0, op1);
>> +    }
> 
> If op0 or op1 have some embedded side-effects, won't you evaluate them
> multiple times?  I'd expect you need to c_save_expr them and make sure the
> actual pointer difference expression uses the result of the save expr too.

Good point, fixed.

> 
>> --- a/libsanitizer/asan/asan_report.cc
>> +++ b/libsanitizer/asan/asan_report.cc
>> @@ -344,14 +344,68 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
>>    if (!flags()->detect_invalid_pointer_pairs) return;
>>    uptr a1 = reinterpret_cast<uptr>(p1);
>>    uptr a2 = reinterpret_cast<uptr>(p2);
>> -  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
>> -  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
>> -  bool valid1 = chunk1.IsAllocated();
>> -  bool valid2 = chunk2.IsAllocated();
>> -  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
>> -    GET_CALLER_PC_BP_SP;
>> -    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
>> +
>> +  if (a1 == a2)
>> +    return;
>> +
>> +  uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
>> +  uptr left = a1 < a2 ? a1 : a2;
>> +  uptr right = a1 < a2 ? a2 : a1;
>> +  if (offset <= 2048) {
>> +    if (__asan_region_is_poisoned (left, offset) == 0)
>> +      return;
>> +  }
> 
> If offset <= 2048 and something is poisoned in between, then it is
> clearly a failure, so you should either goto the error or duplicate
> the two lines inside of the outer if above.

Done that.

> 
>> +
>> +  uptr shadow_offset1, shadow_offset2;
>> +  bool valid1, valid2;
>> +  {
>> +    ThreadRegistryLock l(&asanThreadRegistry());
>> +    valid1 = GetStackVariableBeginning(left, &shadow_offset1);
>>    }
>> +
>> +  // check whether a1 is a stack memory pointer
>> +  if (valid1) {
>> +    {
>> +      ThreadRegistryLock l(&asanThreadRegistry());
>> +      valid2 = GetStackVariableBeginning(right - 1, &shadow_offset2);
> 
> Why do you take the registery lock twice?  Just do:

Yep, once is OK. However, we need to have the lock in a different scope than:
GET_CALLER_PC_BP_SP;
ReportInvalidPointerPair(pc, bp, sp, a1, a2);

Otherwise we'll reach a deadlock.

>   {
>     ThreadRegistryLock l(&asanThreadRegistry());
>     if (GetStackVariableBeginning(left, &shadow_offset1))
>       {
>         if (GetStackVariableBeginning(right - 1, &shadow_offset2) &&
> 	    shadow_offset1 == shadow_offset2)
> 	  return;
> // goto do_error; or:
> 	GET_CALLER_PC_BP_SP;
> 	ReportInvalidPointerPair(pc, bp, sp, a1, a2);
> 	return;
>       }
>   }
> 
>> +    }
>> +
>> +    if (valid2 && shadow_offset1 == shadow_offset2)
>> +      return;
>> +  }
>> +  // check whether a1 is a heap memory address
>> +  else {
>> +    HeapAddressDescription hdesc1;
>> +    valid1 = GetHeapAddressInformation(a1, 0, &hdesc1);
>> +
>> +    if (valid1 && hdesc1.chunk_access.access_type == kAccessTypeInside) {
>> +      HeapAddressDescription hdesc2;
>> +      valid2 = GetHeapAddressInformation(a2, 0, &hdesc2);
> 
> Again, no need for valid1/valid2.  Why do you use above left and right - 1
> and here a1 and a2?  Shouldn't it be always left and right - 1?

valid{1,2} removed. Now using a{1,2} just for error report purpose.

Martin

> 
> 	Jakub
>
From 68710d309cedf737ef333e82f33a8f2c9fd43c25 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 5 Oct 2017 12:14:25 +0200
Subject: [PATCH] Add -fsanitize=pointer-{compare,subtract}.

gcc/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* ipa-inline.c (sanitize_attrs_match_for_inline_p): Add handling
	of SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE): Likewise.
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.

gcc/c/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* c-typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(build_binary_op): Similar for pointer comparison.

gcc/testsuite/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* gcc.dg/asan/pointer-compare-1.c: New test.
	* gcc.dg/asan/pointer-compare-2.c: New test.
	* gcc.dg/asan/pointer-subtract-1.c: New test.
	* gcc.dg/asan/pointer-subtract-2.c: New test.
---
 gcc/c/c-typeck.c                               | 34 ++++++++++--
 gcc/doc/invoke.texi                            | 22 ++++++++
 gcc/flag-types.h                               |  2 +
 gcc/ipa-inline.c                               |  8 ++-
 gcc/opts.c                                     | 15 +++++
 gcc/sanitizer.def                              |  4 ++
 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c  | 77 ++++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c  | 76 +++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c | 41 ++++++++++++++
 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c | 32 +++++++++++
 libsanitizer/asan/asan_descriptions.cc         | 29 ++++++++++
 libsanitizer/asan/asan_descriptions.h          |  5 ++
 libsanitizer/asan/asan_report.cc               | 59 +++++++++++++++++---
 libsanitizer/asan/asan_thread.cc               | 23 ++++++++
 libsanitizer/asan/asan_thread.h                |  3 +
 15 files changed, 417 insertions(+), 13 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c

diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index cb9c589e061..ef0818ce994 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -96,7 +96,7 @@ static tree lookup_field (tree, tree);
 static int convert_arguments (location_t, vec<location_t>, tree,
 			      vec<tree, va_gc> *, vec<tree, va_gc> *, tree,
 			      tree);
-static tree pointer_diff (location_t, tree, tree);
+static tree pointer_diff (location_t, tree, tree, tree *);
 static tree convert_for_assignment (location_t, location_t, tree, tree, tree,
 				    enum impl_conv, bool, tree, tree, int);
 static tree valid_compound_expr_initializer (tree, tree);
@@ -3779,10 +3779,11 @@ parser_build_binary_op (location_t location, enum tree_code code,
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
-pointer_diff (location_t loc, tree op0, tree op1)
+pointer_diff (location_t loc, tree op0, tree op1, tree *instrument_expr)
 {
   tree restype = ptrdiff_type_node;
   tree result, inttype;
@@ -3826,6 +3827,19 @@ pointer_diff (location_t loc, tree op0, tree op1)
     pedwarn (loc, OPT_Wpointer_arith,
 	     "pointer to a function used in subtraction");
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      gcc_assert (current_function_decl != NULL_TREE);
+
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr
+	= build_call_expr_loc (loc, tt, 2, c_fully_fold (op0, false, NULL),
+			       c_fully_fold (op1, false, NULL));
+    }
+
   /* First do the subtraction as integers;
      then drop through to build the divide operator.
      Do not do default conversions on the minus operator
@@ -11188,7 +11202,7 @@ build_binary_op (location_t location, enum tree_code code,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && comp_target_types (location, type0, type1))
 	{
-	  ret = pointer_diff (location, op0, op1);
+	  ret = pointer_diff (location, op0, op1, &instrument_expr);
 	  goto return_build_binary_op;
 	}
       /* Handle pointer minus int.  Just like pointer plus int.  */
@@ -11707,6 +11721,18 @@ build_binary_op (location_t location, enum tree_code code,
 	      pedwarn (location, 0,
 		       "comparison of distinct pointer types lacks a cast");
 	    }
+
+	  if (sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	    {
+	      gcc_assert (current_function_decl != NULL_TREE);
+
+	      op0 = save_expr (op0);
+	      op1 = save_expr (op1);
+
+	      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	      instrument_expr
+		= build_call_expr_loc (location, tt, 2, op0, op1);
+	    }
 	}
       else if (code0 == POINTER_TYPE && null_pointer_constant_p (orig_op1))
 	{
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 4e7dfb33c31..6cec1fea033 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10960,6 +10960,28 @@ Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 1f439d35b07..74464651e00 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_VPTR = 1UL << 22,
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
+  SANITIZE_POINTER_COMPARE = 1UL << 25,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 26,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index dd46cb61362..d60432cded3 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -263,8 +263,12 @@ sanitize_attrs_match_for_inline_p (const_tree caller, const_tree callee)
   if (!caller || !callee)
     return true;
 
-  return sanitize_flags_p (SANITIZE_ADDRESS, caller)
-    == sanitize_flags_p (SANITIZE_ADDRESS, callee);
+  return ((sanitize_flags_p (SANITIZE_ADDRESS, caller)
+	   == sanitize_flags_p (SANITIZE_ADDRESS, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_COMPARE, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_COMPARE, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, callee)));
 }
 
 /* Used for flags where it is safe to inline when caller's value is
diff --git a/gcc/opts.c b/gcc/opts.c
index adf3d89851d..f39ead7e1cf 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -952,6 +952,19 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
   if (opts->x_dwarf_split_debug_info)
     opts->x_debug_generate_pub_sections = 2;
 
+  if ((opts->x_flag_sanitize
+       & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
+    {
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE)
+	error_at (loc,
+		  "%<-fsanitize=pointer-compare%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
+	error_at (loc,
+		  "%<-fsanitize=pointer-subtract%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+    }
+
   /* Userspace and kernel ASan conflict with each other.  */
   if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
       && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
@@ -1496,6 +1509,8 @@ const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
+  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 9d963f05c21..d06f68ba66e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..0e448cf2a3a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
@@ -0,0 +1,77 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p, char *q)
+{
+  return p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+char small_global[7] = {};
+char large_global[5000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+  __builtin_free (heap1);
+  __builtin_free (heap2);
+
+  heap1 = (char *)__builtin_malloc(1024);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 1025);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1 + 1024, heap1 + 1025);
+  __builtin_free (heap1);
+
+  heap1 = (char *)__builtin_malloc(4096);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 4097);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  char *p = &small_global[0];
+  foo (p, p); // OK
+  foo (p, p + 7); // OK
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p + 8);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p + 8);
+
+  p = &large_global[0];
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &small_global[0]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..4967064634f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
@@ -0,0 +1,76 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+int baz(char *p, char *q)
+{
+  return p != 0 && p < q;
+}
+
+char global[8192] = {};
+char small_global[7] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = foo(p);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(1024);
+  bar(p, p + 1024);
+  bar(p + 1024, p + 1023);
+  bar(p + 1, p + 1023);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(4096);
+  bar(p, p + 4096);
+  bar(p + 10, p + 100);
+  bar(p + 1024, p + 4096);
+  bar(p + 4095, p + 4096);
+  bar(p + 4095, p + 4094);
+  bar(p + 100, p + 4096);
+  bar(p + 100, p + 4094);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[1]);
+  bar(&global[1], &global[2]);
+  bar(&global[2], &global[1]);
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[7000]);
+  bar(&global[500], &global[10]);
+  p = &global[0];
+  bar(p, p + 8192);
+  p = &global[8000];
+  bar(p, p + 192);
+
+  p = &small_global[0];
+  bar (p, p + 1);
+  bar (p, p + 7);
+  bar (p + 7, p + 1);
+  bar (p + 6, p + 7);
+  bar (p + 7, p + 7);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  baz(0, &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..10792264f2e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int foo(char *p, char *q)
+{
+  return p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
+
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..17cf0e6711d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
@@ -0,0 +1,32 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = bar(p, p - 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/libsanitizer/asan/asan_descriptions.cc b/libsanitizer/asan/asan_descriptions.cc
index 35d1619f2d9..934618c3181 100644
--- a/libsanitizer/asan/asan_descriptions.cc
+++ b/libsanitizer/asan/asan_descriptions.cc
@@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
   return true;
 }
 
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
+{
+  AsanThread *t = FindThreadByStackAddress(addr);
+  if (!t) return false;
+
+  *shadow_addr = t->GetStackFrameVariableBeginning (addr);
+  return true;
+}
+
 static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
                                           uptr access_size, uptr prev_var_end,
                                           uptr next_var_beg) {
@@ -330,6 +339,26 @@ void GlobalAddressDescription::Print(const char *bug_type) const {
   }
 }
 
+bool GlobalAddressDescription::PointsInsideASameVariable
+  (const GlobalAddressDescription &other) const
+{
+  if (size == 0 || other.size == 0)
+    return false;
+
+  for (unsigned i = 0; i < size; i++)
+    for (unsigned j = 0; j < other.size; j++) {
+      if (globals[i].beg == other.globals[j].beg
+	  && globals[i].beg <= addr
+	  && other.globals[j].beg <= other.addr
+	  && (addr + access_size) < (globals[i].beg + globals[i].size)
+	  && ((other.addr + other.access_size)
+	    < (other.globals[j].beg + other.globals[j].size)))
+	return true;
+    }
+
+  return false;
+}
+
 void StackAddressDescription::Print() const {
   Decorator d;
   char tname[128];
diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
index 584b9ba6491..35baec6f2f9 100644
--- a/libsanitizer/asan/asan_descriptions.h
+++ b/libsanitizer/asan/asan_descriptions.h
@@ -138,6 +138,7 @@ struct StackAddressDescription {
 
 bool GetStackAddressInformation(uptr addr, uptr access_size,
                                 StackAddressDescription *descr);
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
 
 struct GlobalAddressDescription {
   uptr addr;
@@ -149,6 +150,10 @@ struct GlobalAddressDescription {
   u8 size;
 
   void Print(const char *bug_type = "") const;
+
+  // Return true when this descriptions points inside a same global variable
+  // as other. Descriptions can have different address within the variable
+  bool PointsInsideASameVariable (const GlobalAddressDescription &other) const;
 };
 
 bool GetGlobalAddressInformation(uptr addr, uptr access_size,
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 84d67646b40..c062c422a15 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -344,14 +344,59 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
-    GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+
+  if (a1 == a2)
+    return;
+
+  uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
+  uptr left = a1 < a2 ? a1 : a2;
+  uptr right = a1 < a2 ? a2 : a1;
+  if (offset <= 2048) {
+    if (__asan_region_is_poisoned (left, offset) == 0)
+      return;
+    else
+      goto do_error;
+  }
+
+  {
+    uptr shadow_offset1, shadow_offset2;
+    ThreadRegistryLock l(&asanThreadRegistry());
+
+    // check whether left is a stack memory pointer
+    if (GetStackVariableBeginning(left, &shadow_offset1)) {
+      if (GetStackVariableBeginning(right - 1, &shadow_offset2)
+	  && shadow_offset1 == shadow_offset2)
+	return;
+      else
+	goto do_error;
+    }
+    // check whether left is a heap memory address
+    else {
+      HeapAddressDescription hdesc1, hdesc2;
+      if (GetHeapAddressInformation(left, 0, &hdesc1)
+	  && hdesc1.chunk_access.access_type == kAccessTypeInside) {
+	if (GetHeapAddressInformation(right, 0, &hdesc2)
+	    && hdesc2.chunk_access.access_type == kAccessTypeInside
+	    && (hdesc1.chunk_access.chunk_begin
+	      == hdesc2.chunk_access.chunk_begin))
+	  return;
+      } else {
+	// check whether left is an address of a global variable
+	GlobalAddressDescription gdesc1, gdesc2;
+	if (GetGlobalAddressInformation(left, 0, &gdesc1)) {
+	  if (GetGlobalAddressInformation(right - 1, 0, &gdesc2)
+	      && gdesc1.PointsInsideASameVariable (gdesc2))
+	    return;
+	} else {
+	  // TODO
+	}
+      }
+    }
   }
+
+do_error:
+  GET_CALLER_PC_BP_SP;
+  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
 
diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
index 818e1261400..0ffca7af35e 100644
--- a/libsanitizer/asan/asan_thread.cc
+++ b/libsanitizer/asan/asan_thread.cc
@@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
   return true;
 }
 
+uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
+{
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  }
+  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom
+	  && (*shadow_ptr != kAsanStackLeftRedzoneMagic
+	  && *shadow_ptr != kAsanStackMidRedzoneMagic
+	  && *shadow_ptr != kAsanStackRightRedzoneMagic)) {
+    shadow_ptr--;
+  }
+
+  return (uptr)shadow_ptr;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
index c51a58ad0bb..edd1d815632 100644
--- a/libsanitizer/asan/asan_thread.h
+++ b/libsanitizer/asan/asan_thread.h
@@ -81,6 +81,9 @@ class AsanThread {
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
 
+  // Return beginning of a stack variable in shadow memory
+  uptr GetStackFrameVariableBeginning(uptr addr);
+
   bool AddrIsInStack(uptr addr);
 
   void DeleteFakeStack(int tid) {
Jakub Jelinek Oct. 13, 2017, 1:13 p.m. UTC | #13
On Fri, Oct 13, 2017 at 02:53:50PM +0200, Martin Liška wrote:
> @@ -3826,6 +3827,19 @@ pointer_diff (location_t loc, tree op0, tree op1)
>      pedwarn (loc, OPT_Wpointer_arith,
>  	     "pointer to a function used in subtraction");
>  
> +  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
> +    {
> +      gcc_assert (current_function_decl != NULL_TREE);
> +
> +      op0 = save_expr (op0);
> +      op1 = save_expr (op1);
> +
> +      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
> +      *instrument_expr
> +	= build_call_expr_loc (loc, tt, 2, c_fully_fold (op0, false, NULL),
> +			       c_fully_fold (op1, false, NULL));
> +    }

Why the c_fully_fold?  Can't that be deferred until it actually is
folded all together later?

> +	  ret = pointer_diff (location, op0, op1, &instrument_expr);
>  	  goto return_build_binary_op;
>  	}
>        /* Handle pointer minus int.  Just like pointer plus int.  */
> @@ -11707,6 +11721,18 @@ build_binary_op (location_t location, enum tree_code code,
>  	      pedwarn (location, 0,
>  		       "comparison of distinct pointer types lacks a cast");
>  	    }
> +
> +	  if (sanitize_flags_p (SANITIZE_POINTER_COMPARE))
> +	    {
> +	      gcc_assert (current_function_decl != NULL_TREE);
> +
> +	      op0 = save_expr (op0);
> +	      op1 = save_expr (op1);
> +
> +	      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
> +	      instrument_expr
> +		= build_call_expr_loc (location, tt, 2, op0, op1);
> +	    }

Is this the right spot for this?  I mean then you don't handle
ptr >= 0 or ptr > 0 and similar or ptr >= 0x12345678.
I know we have warnings or pedwarns for those, still I think it would be
better to handle the above e.g. before
      if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
           || truth_value_p (TREE_CODE (orig_op0)))
          ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
             || truth_value_p (TREE_CODE (orig_op1))))
        maybe_warn_bool_compare (location, code, orig_op0, orig_op1);
by testing if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
	       && sanitize_flags_p (SANITIZE_POINTER_COMPARE))

What about the C++ FE?  Or is pointer comparison well defined there?
What about pointer subtraction? My memory is fuzzy.

> +// { dg-options "-fsanitize=pointer-compare -O0" }

Please use -fsanitize=address,pointer-compare
etc. in the testcases, so that it is an example to users who don't know
we have implicit -fsanitize=address for these tests.
> +  if (offset <= 2048) {
> +    if (__asan_region_is_poisoned (left, offset) == 0)

I think the LLVM coding conventions don't want a space before ( above.

> +    // check whether left is a stack memory pointer
> +    if (GetStackVariableBeginning(left, &shadow_offset1)) {
> +      if (GetStackVariableBeginning(right - 1, &shadow_offset2)
> +	  && shadow_offset1 == shadow_offset2)

Nor && at the beginning of the line (they want it at the end of previous
one).

> +	return;
> +      else
> +	goto do_error;
> +    }

If you have goto do_error; for all cases, then you don't need to indent
further stuff into else ... further and further.

> +    // check whether left is a heap memory address
> +    else {
> +      HeapAddressDescription hdesc1, hdesc2;
> +      if (GetHeapAddressInformation(left, 0, &hdesc1)
> +	  && hdesc1.chunk_access.access_type == kAccessTypeInside) {
> +	if (GetHeapAddressInformation(right, 0, &hdesc2)
> +	    && hdesc2.chunk_access.access_type == kAccessTypeInside
> +	    && (hdesc1.chunk_access.chunk_begin
> +	      == hdesc2.chunk_access.chunk_begin))
> +	  return;

So, here one is a heap object and the other is not.  That should be an
do_error, right?

> +      } else {
> +	// check whether left is an address of a global variable
> +	GlobalAddressDescription gdesc1, gdesc2;
> +	if (GetGlobalAddressInformation(left, 0, &gdesc1)) {
> +	  if (GetGlobalAddressInformation(right - 1, 0, &gdesc2)
> +	      && gdesc1.PointsInsideASameVariable (gdesc2))
> +	    return;
> +	} else {
> +	  // TODO

Either goto do_error; here too, or do the if (offset <= 16384) case here.
Guess upstream wouldn't like it with a TODO spot.

	Jakub
Martin Liška Oct. 16, 2017, 11:57 a.m. UTC | #14
On 10/13/2017 03:13 PM, Jakub Jelinek wrote:
> On Fri, Oct 13, 2017 at 02:53:50PM +0200, Martin Liška wrote:
>> @@ -3826,6 +3827,19 @@ pointer_diff (location_t loc, tree op0, tree op1)
>>      pedwarn (loc, OPT_Wpointer_arith,
>>  	     "pointer to a function used in subtraction");
>>  
>> +  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
>> +    {
>> +      gcc_assert (current_function_decl != NULL_TREE);
>> +
>> +      op0 = save_expr (op0);
>> +      op1 = save_expr (op1);
>> +
>> +      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
>> +      *instrument_expr
>> +	= build_call_expr_loc (loc, tt, 2, c_fully_fold (op0, false, NULL),
>> +			       c_fully_fold (op1, false, NULL));
>> +    }
> 

Hello Jakub.

> Why the c_fully_fold?  Can't that be deferred until it actually is
> folded all together later?

Yes, now it's not needed as I use save_expr. I hit some ICE before I switched
to use save_expr. That's why I put it there.

> 
>> +	  ret = pointer_diff (location, op0, op1, &instrument_expr);
>>  	  goto return_build_binary_op;
>>  	}
>>        /* Handle pointer minus int.  Just like pointer plus int.  */
>> @@ -11707,6 +11721,18 @@ build_binary_op (location_t location, enum tree_code code,
>>  	      pedwarn (location, 0,
>>  		       "comparison of distinct pointer types lacks a cast");
>>  	    }
>> +
>> +	  if (sanitize_flags_p (SANITIZE_POINTER_COMPARE))
>> +	    {
>> +	      gcc_assert (current_function_decl != NULL_TREE);
>> +
>> +	      op0 = save_expr (op0);
>> +	      op1 = save_expr (op1);
>> +
>> +	      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
>> +	      instrument_expr
>> +		= build_call_expr_loc (location, tt, 2, op0, op1);
>> +	    }
> 
> Is this the right spot for this?  I mean then you don't handle
> ptr >= 0 or ptr > 0 and similar or ptr >= 0x12345678.
> I know we have warnings or pedwarns for those, still I think it would be
> better to handle the above e.g. before
>       if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
>            || truth_value_p (TREE_CODE (orig_op0)))
>           ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
>              || truth_value_p (TREE_CODE (orig_op1))))
>         maybe_warn_bool_compare (location, code, orig_op0, orig_op1);
> by testing if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
> 	       && sanitize_flags_p (SANITIZE_POINTER_COMPARE))

Agree.

> 
> What about the C++ FE?  Or is pointer comparison well defined there?
> What about pointer subtraction? My memory is fuzzy.

No, you're right, I basically forgot:

```
From § 5.9 of the C++11 standard:

If two pointers p and q of the same type point to different objects that
are not members of the same object or elements of the same array or to
different functions, or if only one of them is null, the results
of p<q, p>q, p<=q, and  p>=q are unspecified.

```

I also moved the tests to c-c++-common/asan/ subfolder.

> 
>> +// { dg-options "-fsanitize=pointer-compare -O0" }
> 
> Please use -fsanitize=address,pointer-compare
> etc. in the testcases, so that it is an example to users who don't know
> we have implicit -fsanitize=address for these tests.
>> +  if (offset <= 2048) {
>> +    if (__asan_region_is_poisoned (left, offset) == 0)
> 
> I think the LLVM coding conventions don't want a space before ( above.
> 
>> +    // check whether left is a stack memory pointer
>> +    if (GetStackVariableBeginning(left, &shadow_offset1)) {
>> +      if (GetStackVariableBeginning(right - 1, &shadow_offset2)
>> +	  && shadow_offset1 == shadow_offset2)
> 
> Nor && at the beginning of the line (they want it at the end of previous
> one).
> 
>> +	return;
>> +      else
>> +	goto do_error;
>> +    }
> 
> If you have goto do_error; for all cases, then you don't need to indent
> further stuff into else ... further and further.
> 
>> +    // check whether left is a heap memory address
>> +    else {
>> +      HeapAddressDescription hdesc1, hdesc2;
>> +      if (GetHeapAddressInformation(left, 0, &hdesc1)
>> +	  && hdesc1.chunk_access.access_type == kAccessTypeInside) {
>> +	if (GetHeapAddressInformation(right, 0, &hdesc2)
>> +	    && hdesc2.chunk_access.access_type == kAccessTypeInside
>> +	    && (hdesc1.chunk_access.chunk_begin
>> +	      == hdesc2.chunk_access.chunk_begin))
>> +	  return;
> 
> So, here one is a heap object and the other is not.  That should be an
> do_error, right?

Yes, coding style should be fixed.

> 
>> +      } else {
>> +	// check whether left is an address of a global variable
>> +	GlobalAddressDescription gdesc1, gdesc2;
>> +	if (GetGlobalAddressInformation(left, 0, &gdesc1)) {
>> +	  if (GetGlobalAddressInformation(right - 1, 0, &gdesc2)
>> +	      && gdesc1.PointsInsideASameVariable (gdesc2))
>> +	    return;
>> +	} else {
>> +	  // TODO
> 
> Either goto do_error; here too, or do the if (offset <= 16384) case here.
> Guess upstream wouldn't like it with a TODO spot.

Agree. Do you feel that it's right moment to trigger review process of libsanitizer
changes?

Thanks,
Martin

> 
> 	Jakub
>
From 92cff481bc39feb9e17075d80e11774ea3085719 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 5 Oct 2017 12:14:25 +0200
Subject: [PATCH] Add -fsanitize=pointer-{compare,subtract}.

gcc/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* ipa-inline.c (sanitize_attrs_match_for_inline_p): Add handling
	of SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE): Likewise.
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.

gcc/c/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* c-typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(build_binary_op): Similar for pointer comparison.

gcc/cp/ChangeLog:

2017-10-16  Martin Liska  <mliska@suse.cz>

	* typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(cp_build_binary_op): Create compound expression if doing an
	instrumentation.

gcc/testsuite/ChangeLog:

2017-10-16  Martin Liska  <mliska@suse.cz>

	* c-c++-common/asan/pointer-compare-1.c: New test.
	* c-c++-common/asan/pointer-compare-2.c: New test.
	* c-c++-common/asan/pointer-subtract-1.c: New test.
	* c-c++-common/asan/pointer-subtract-2.c: New test.
---
 gcc/c/c-typeck.c                                   | 33 +++++++--
 gcc/cp/typeck.c                                    | 43 +++++++++--
 gcc/doc/invoke.texi                                | 22 ++++++
 gcc/flag-types.h                                   |  2 +
 gcc/ipa-inline.c                                   |  8 ++-
 gcc/opts.c                                         | 15 ++++
 gcc/sanitizer.def                                  |  4 ++
 .../c-c++-common/asan/pointer-compare-1.c          | 83 ++++++++++++++++++++++
 .../c-c++-common/asan/pointer-compare-2.c          | 76 ++++++++++++++++++++
 .../c-c++-common/asan/pointer-subtract-1.c         | 41 +++++++++++
 .../c-c++-common/asan/pointer-subtract-2.c         | 32 +++++++++
 libsanitizer/asan/asan_descriptions.cc             | 29 ++++++++
 libsanitizer/asan/asan_descriptions.h              |  5 ++
 libsanitizer/asan/asan_report.cc                   | 57 +++++++++++++--
 libsanitizer/asan/asan_thread.cc                   | 23 ++++++
 libsanitizer/asan/asan_thread.h                    |  3 +
 16 files changed, 458 insertions(+), 18 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c

diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index cb9c589e061..d623418288f 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -96,7 +96,7 @@ static tree lookup_field (tree, tree);
 static int convert_arguments (location_t, vec<location_t>, tree,
 			      vec<tree, va_gc> *, vec<tree, va_gc> *, tree,
 			      tree);
-static tree pointer_diff (location_t, tree, tree);
+static tree pointer_diff (location_t, tree, tree, tree *);
 static tree convert_for_assignment (location_t, location_t, tree, tree, tree,
 				    enum impl_conv, bool, tree, tree, int);
 static tree valid_compound_expr_initializer (tree, tree);
@@ -3779,10 +3779,11 @@ parser_build_binary_op (location_t location, enum tree_code code,
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
-pointer_diff (location_t loc, tree op0, tree op1)
+pointer_diff (location_t loc, tree op0, tree op1, tree *instrument_expr)
 {
   tree restype = ptrdiff_type_node;
   tree result, inttype;
@@ -3826,6 +3827,17 @@ pointer_diff (location_t loc, tree op0, tree op1)
     pedwarn (loc, OPT_Wpointer_arith,
 	     "pointer to a function used in subtraction");
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      gcc_assert (current_function_decl != NULL_TREE);
+
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction as integers;
      then drop through to build the divide operator.
      Do not do default conversions on the minus operator
@@ -11188,7 +11200,7 @@ build_binary_op (location_t location, enum tree_code code,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && comp_target_types (location, type0, type1))
 	{
-	  ret = pointer_diff (location, op0, op1);
+	  ret = pointer_diff (location, op0, op1, &instrument_expr);
 	  goto return_build_binary_op;
 	}
       /* Handle pointer minus int.  Just like pointer plus int.  */
@@ -11738,6 +11750,19 @@ build_binary_op (location_t location, enum tree_code code,
 	  result_type = type1;
 	  pedwarn (location, 0, "comparison between pointer and integer");
 	}
+
+      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
+	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	{
+	  gcc_assert (current_function_decl != NULL_TREE);
+
+	  op0 = save_expr (op0);
+	  op1 = save_expr (op1);
+
+	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
+	}
+
       if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
 	   || truth_value_p (TREE_CODE (orig_op0)))
 	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 08b2ae555e6..25087934910 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -54,7 +54,7 @@ static tree rationalize_conditional_expr (enum tree_code, tree,
 static int comp_ptr_ttypes_real (tree, tree, int);
 static bool comp_except_types (tree, tree, bool);
 static bool comp_array_types (const_tree, const_tree, bool);
-static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t);
+static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t, tree *);
 static tree get_delta_difference (tree, tree, bool, bool, tsubst_flags_t);
 static void casts_away_constness_r (tree *, tree *, tsubst_flags_t);
 static bool casts_away_constness (tree, tree, tsubst_flags_t);
@@ -4318,8 +4318,16 @@ cp_build_binary_op (location_t location,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (type0),
 							TREE_TYPE (type1)))
-	return pointer_diff (location, op0, op1,
-			     common_pointer_type (type0, type1), complain);
+	{
+	  result = pointer_diff (location, op0, op1,
+				 common_pointer_type (type0, type1), complain,
+				 &instrument_expr);
+	  if (instrument_expr != NULL)
+	    result = build2 (COMPOUND_EXPR, TREE_TYPE (result),
+			     instrument_expr, result);
+
+	  return result;
+	}
       /* In all other cases except pointer - int, the usual arithmetic
 	 rules apply.  */
       else if (!(code0 == POINTER_TYPE && code1 == INTEGER_TYPE))
@@ -5008,6 +5016,19 @@ cp_build_binary_op (location_t location,
           else
             return error_mark_node;
 	}
+
+      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
+	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	{
+	  gcc_assert (current_function_decl != NULL_TREE);
+
+	  op0 = save_expr (op0);
+	  op1 = save_expr (op1);
+
+	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
+	}
+
       break;
 
     case UNORDERED_EXPR:
@@ -5363,11 +5384,12 @@ cp_pointer_int_sum (location_t loc, enum tree_code resultcode, tree ptrop,
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
 pointer_diff (location_t loc, tree op0, tree op1, tree ptrtype,
-	      tsubst_flags_t complain)
+	      tsubst_flags_t complain, tree *instrument_expr)
 {
   tree result;
   tree restype = ptrdiff_type_node;
@@ -5401,6 +5423,17 @@ pointer_diff (location_t loc, tree op0, tree op1, tree ptrtype,
 	return error_mark_node;
     }
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      gcc_assert (current_function_decl != NULL_TREE);
+
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction as integers;
      then drop through to build the divide operator.  */
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 4e7dfb33c31..6cec1fea033 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10960,6 +10960,28 @@ Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 1f439d35b07..74464651e00 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_VPTR = 1UL << 22,
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
+  SANITIZE_POINTER_COMPARE = 1UL << 25,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 26,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index dd46cb61362..d60432cded3 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -263,8 +263,12 @@ sanitize_attrs_match_for_inline_p (const_tree caller, const_tree callee)
   if (!caller || !callee)
     return true;
 
-  return sanitize_flags_p (SANITIZE_ADDRESS, caller)
-    == sanitize_flags_p (SANITIZE_ADDRESS, callee);
+  return ((sanitize_flags_p (SANITIZE_ADDRESS, caller)
+	   == sanitize_flags_p (SANITIZE_ADDRESS, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_COMPARE, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_COMPARE, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, callee)));
 }
 
 /* Used for flags where it is safe to inline when caller's value is
diff --git a/gcc/opts.c b/gcc/opts.c
index adf3d89851d..f39ead7e1cf 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -952,6 +952,19 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
   if (opts->x_dwarf_split_debug_info)
     opts->x_debug_generate_pub_sections = 2;
 
+  if ((opts->x_flag_sanitize
+       & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
+    {
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE)
+	error_at (loc,
+		  "%<-fsanitize=pointer-compare%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
+	error_at (loc,
+		  "%<-fsanitize=pointer-subtract%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+    }
+
   /* Userspace and kernel ASan conflict with each other.  */
   if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
       && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
@@ -1496,6 +1509,8 @@ const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
+  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 9d963f05c21..d06f68ba66e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c b/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..5f97d954a37
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
@@ -0,0 +1,83 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p, char *q)
+{
+  return p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+char small_global[7] = {};
+char large_global[5000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+  __builtin_free (heap1);
+  __builtin_free (heap2);
+
+  heap1 = (char *)__builtin_malloc(1024);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 1025);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1 + 1024, heap1 + 1025);
+  __builtin_free (heap1);
+
+  heap1 = (char *)__builtin_malloc(4096);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 4097);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, 0);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  char *p = &small_global[0];
+  foo (p, p); // OK
+  foo (p, p + 7); // OK
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p + 8);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p + 8);
+
+  p = &large_global[0];
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &small_global[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, 0);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&stack1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, 0);
+  return 1;
+}
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c b/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..4967064634f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
@@ -0,0 +1,76 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+int baz(char *p, char *q)
+{
+  return p != 0 && p < q;
+}
+
+char global[8192] = {};
+char small_global[7] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = foo(p);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(1024);
+  bar(p, p + 1024);
+  bar(p + 1024, p + 1023);
+  bar(p + 1, p + 1023);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(4096);
+  bar(p, p + 4096);
+  bar(p + 10, p + 100);
+  bar(p + 1024, p + 4096);
+  bar(p + 4095, p + 4096);
+  bar(p + 4095, p + 4094);
+  bar(p + 100, p + 4096);
+  bar(p + 100, p + 4094);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[1]);
+  bar(&global[1], &global[2]);
+  bar(&global[2], &global[1]);
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[7000]);
+  bar(&global[500], &global[10]);
+  p = &global[0];
+  bar(p, p + 8192);
+  p = &global[8000];
+  bar(p, p + 192);
+
+  p = &small_global[0];
+  bar (p, p + 1);
+  bar (p, p + 7);
+  bar (p + 7, p + 1);
+  bar (p + 6, p + 7);
+  bar (p + 7, p + 7);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  baz(0, &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c b/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..10792264f2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int foo(char *p, char *q)
+{
+  return p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
+
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c b/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..17cf0e6711d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c
@@ -0,0 +1,32 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = bar(p, p - 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/libsanitizer/asan/asan_descriptions.cc b/libsanitizer/asan/asan_descriptions.cc
index 35d1619f2d9..8901772e0da 100644
--- a/libsanitizer/asan/asan_descriptions.cc
+++ b/libsanitizer/asan/asan_descriptions.cc
@@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
   return true;
 }
 
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
+{
+  AsanThread *t = FindThreadByStackAddress(addr);
+  if (!t) return false;
+
+  *shadow_addr = t->GetStackFrameVariableBeginning(addr);
+  return true;
+}
+
 static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
                                           uptr access_size, uptr prev_var_end,
                                           uptr next_var_beg) {
@@ -330,6 +339,26 @@ void GlobalAddressDescription::Print(const char *bug_type) const {
   }
 }
 
+bool GlobalAddressDescription::PointsInsideASameVariable
+  (const GlobalAddressDescription &other) const
+{
+  if (size == 0 || other.size == 0)
+    return false;
+
+  for (unsigned i = 0; i < size; i++)
+    for (unsigned j = 0; j < other.size; j++) {
+      if (globals[i].beg == other.globals[j].beg &&
+	  globals[i].beg <= addr &&
+	  other.globals[j].beg <= other.addr &&
+	  (addr + access_size) < (globals[i].beg + globals[i].size) &&
+	  ((other.addr + other.access_size)
+	   < (other.globals[j].beg + other.globals[j].size)))
+	return true;
+    }
+
+  return false;
+}
+
 void StackAddressDescription::Print() const {
   Decorator d;
   char tname[128];
diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
index 584b9ba6491..35baec6f2f9 100644
--- a/libsanitizer/asan/asan_descriptions.h
+++ b/libsanitizer/asan/asan_descriptions.h
@@ -138,6 +138,7 @@ struct StackAddressDescription {
 
 bool GetStackAddressInformation(uptr addr, uptr access_size,
                                 StackAddressDescription *descr);
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
 
 struct GlobalAddressDescription {
   uptr addr;
@@ -149,6 +150,10 @@ struct GlobalAddressDescription {
   u8 size;
 
   void Print(const char *bug_type = "") const;
+
+  // Return true when this descriptions points inside a same global variable
+  // as other. Descriptions can have different address within the variable
+  bool PointsInsideASameVariable (const GlobalAddressDescription &other) const;
 };
 
 bool GetGlobalAddressInformation(uptr addr, uptr access_size,
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 84d67646b40..7eea02de04a 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -344,14 +344,57 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
-    GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+
+  if (a1 == a2)
+    return;
+
+  uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
+  uptr left = a1 < a2 ? a1 : a2;
+  uptr right = a1 < a2 ? a2 : a1;
+  if (offset <= 2048) {
+    if (__asan_region_is_poisoned(left, offset) == 0)
+      return;
+    else
+      goto do_error;
+  }
+
+  {
+    uptr shadow_offset1, shadow_offset2;
+    ThreadRegistryLock l(&asanThreadRegistry());
+
+    // check whether left is a stack memory pointer
+    if (GetStackVariableBeginning(left, &shadow_offset1)) {
+      if (GetStackVariableBeginning(right - 1, &shadow_offset2) &&
+	  shadow_offset1 == shadow_offset2)
+	return;
+      else
+	goto do_error;
+    }
+    // check whether left is a heap memory address
+    HeapAddressDescription hdesc1, hdesc2;
+    if (GetHeapAddressInformation(left, 0, &hdesc1) &&
+	hdesc1.chunk_access.access_type == kAccessTypeInside) {
+      if (GetHeapAddressInformation(right, 0, &hdesc2) &&
+	  hdesc2.chunk_access.access_type == kAccessTypeInside &&
+	  (hdesc1.chunk_access.chunk_begin
+	   == hdesc2.chunk_access.chunk_begin))
+	return;
+      else
+	goto do_error;
+    }
+    // check whether left is an address of a global variable
+    GlobalAddressDescription gdesc1, gdesc2;
+    if (GetGlobalAddressInformation(left, 0, &gdesc1)) {
+      if (GetGlobalAddressInformation(right - 1, 0, &gdesc2) &&
+	  gdesc1.PointsInsideASameVariable (gdesc2))
+	return;
+    } else
+      goto do_error;
   }
+
+do_error:
+  GET_CALLER_PC_BP_SP;
+  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
 
diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
index 818e1261400..eb6db52905d 100644
--- a/libsanitizer/asan/asan_thread.cc
+++ b/libsanitizer/asan/asan_thread.cc
@@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
   return true;
 }
 
+uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
+{
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  }
+  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom &&
+	 (*shadow_ptr != kAsanStackLeftRedzoneMagic &&
+	  *shadow_ptr != kAsanStackMidRedzoneMagic &&
+	  *shadow_ptr != kAsanStackRightRedzoneMagic)) {
+    shadow_ptr--;
+  }
+
+  return (uptr)shadow_ptr;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
index c51a58ad0bb..edd1d815632 100644
--- a/libsanitizer/asan/asan_thread.h
+++ b/libsanitizer/asan/asan_thread.h
@@ -81,6 +81,9 @@ class AsanThread {
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
 
+  // Return beginning of a stack variable in shadow memory
+  uptr GetStackFrameVariableBeginning(uptr addr);
+
   bool AddrIsInStack(uptr addr);
 
   void DeleteFakeStack(int tid) {
Jakub Jelinek Oct. 16, 2017, 12:21 p.m. UTC | #15
On Mon, Oct 16, 2017 at 01:57:59PM +0200, Martin Liška wrote:
> Agree. Do you feel that it's right moment to trigger review process of libsanitizer
> changes?

Yes.  We don't have that much time left to get it through...

> --- a/libsanitizer/asan/asan_report.cc
> +++ b/libsanitizer/asan/asan_report.cc
> +  {
> +    uptr shadow_offset1, shadow_offset2;
> +    ThreadRegistryLock l(&asanThreadRegistry());
> +
> +    // check whether left is a stack memory pointer
> +    if (GetStackVariableBeginning(left, &shadow_offset1)) {
> +      if (GetStackVariableBeginning(right - 1, &shadow_offset2) &&
> +	  shadow_offset1 == shadow_offset2)
> +	return;
> +      else
> +	goto do_error;
> +    }

Do you need the ThreadRegistryLock for the following calls?
If not, the } should be closed and it should be reindented.

> +    // check whether left is a heap memory address
> +    HeapAddressDescription hdesc1, hdesc2;
> +    if (GetHeapAddressInformation(left, 0, &hdesc1) &&
> +	hdesc1.chunk_access.access_type == kAccessTypeInside) {
> +      if (GetHeapAddressInformation(right, 0, &hdesc2) &&
> +	  hdesc2.chunk_access.access_type == kAccessTypeInside &&
> +	  (hdesc1.chunk_access.chunk_begin
> +	   == hdesc2.chunk_access.chunk_begin))
> +	return;
> +      else
> +	goto do_error;
> +    }
> +    // check whether left is an address of a global variable
> +    GlobalAddressDescription gdesc1, gdesc2;
> +    if (GetGlobalAddressInformation(left, 0, &gdesc1)) {
> +      if (GetGlobalAddressInformation(right - 1, 0, &gdesc2) &&
> +	  gdesc1.PointsInsideASameVariable (gdesc2))
> +	return;
> +    } else
> +      goto do_error;

??  If we don't know anything about the left object, doing a goto do_error;
sounds dangerous, it could be say mmapped region or whatever else.

Though, we could at that spot at least check if right isn't one
of the 3 kinds of regions we track and if yes, error out.
So perhaps move the else goto do_error; inside of the {} and
do
  if (GetStackVariableBeginning(right - 1, &shadow_offset2) ||
      GetHeapAddressInformation(right, 0, &hdesc2) ||
      GetGlobalAddressInformation(right - 1, 0, &gdesc2))
    goto do_error;
  return;
(if the lock above is released, you'd of course need to retake it for
if (GetStackVariableBeginning(right - 1, &shadow_offset2)).

	Jakub
Martin Liška Oct. 16, 2017, 1:38 p.m. UTC | #16
On 10/16/2017 02:21 PM, Jakub Jelinek wrote:
> On Mon, Oct 16, 2017 at 01:57:59PM +0200, Martin Liška wrote:
>> Agree. Do you feel that it's right moment to trigger review process of libsanitizer
>> changes?
> 
> Yes.  We don't have that much time left to get it through...

Good, I've triggered regression tests. Will send it to phsabricator later this evening.

> 
>> --- a/libsanitizer/asan/asan_report.cc
>> +++ b/libsanitizer/asan/asan_report.cc
>> +  {
>> +    uptr shadow_offset1, shadow_offset2;
>> +    ThreadRegistryLock l(&asanThreadRegistry());
>> +
>> +    // check whether left is a stack memory pointer
>> +    if (GetStackVariableBeginning(left, &shadow_offset1)) {
>> +      if (GetStackVariableBeginning(right - 1, &shadow_offset2) &&
>> +	  shadow_offset1 == shadow_offset2)
>> +	return;
>> +      else
>> +	goto do_error;
>> +    }
> 
> Do you need the ThreadRegistryLock for the following calls?
> If not, the } should be closed and it should be reindented.

Yes, we do.

> 
>> +    // check whether left is a heap memory address
>> +    HeapAddressDescription hdesc1, hdesc2;
>> +    if (GetHeapAddressInformation(left, 0, &hdesc1) &&
>> +	hdesc1.chunk_access.access_type == kAccessTypeInside) {
>> +      if (GetHeapAddressInformation(right, 0, &hdesc2) &&
>> +	  hdesc2.chunk_access.access_type == kAccessTypeInside &&
>> +	  (hdesc1.chunk_access.chunk_begin
>> +	   == hdesc2.chunk_access.chunk_begin))
>> +	return;
>> +      else
>> +	goto do_error;
>> +    }
>> +    // check whether left is an address of a global variable
>> +    GlobalAddressDescription gdesc1, gdesc2;
>> +    if (GetGlobalAddressInformation(left, 0, &gdesc1)) {
>> +      if (GetGlobalAddressInformation(right - 1, 0, &gdesc2) &&
>> +	  gdesc1.PointsInsideASameVariable (gdesc2))
>> +	return;
>> +    } else
>> +      goto do_error;
> 
> ??  If we don't know anything about the left object, doing a goto do_error;
> sounds dangerous, it could be say mmapped region or whatever else.

Good point!

> 
> Though, we could at that spot at least check if right isn't one
> of the 3 kinds of regions we track and if yes, error out.
> So perhaps move the else goto do_error; inside of the {} and
> do
>   if (GetStackVariableBeginning(right - 1, &shadow_offset2) ||
>       GetHeapAddressInformation(right, 0, &hdesc2) ||
>       GetGlobalAddressInformation(right - 1, 0, &gdesc2))
>     goto do_error;
>   return;
> (if the lock above is released, you'd of course need to retake it for
> if (GetStackVariableBeginning(right - 1, &shadow_offset2)).

Yes, should be fixed now.

Martin

> 
> 	Jakub
>
From 55e226413b9b3533b4167a4895b40f66cd665f11 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 5 Oct 2017 12:14:25 +0200
Subject: [PATCH] Add -fsanitize=pointer-{compare,subtract}.

gcc/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* ipa-inline.c (sanitize_attrs_match_for_inline_p): Add handling
	of SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE): Likewise.
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.

gcc/c/ChangeLog:

2017-10-13  Martin Liska  <mliska@suse.cz>

	* c-typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(build_binary_op): Similar for pointer comparison.

gcc/cp/ChangeLog:

2017-10-16  Martin Liska  <mliska@suse.cz>

	* typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(cp_build_binary_op): Create compound expression if doing an
	instrumentation.

gcc/testsuite/ChangeLog:

2017-10-16  Martin Liska  <mliska@suse.cz>

	* c-c++-common/asan/pointer-compare-1.c: New test.
	* c-c++-common/asan/pointer-compare-2.c: New test.
	* c-c++-common/asan/pointer-subtract-1.c: New test.
	* c-c++-common/asan/pointer-subtract-2.c: New test.
---
 gcc/c/c-typeck.c                                   | 33 +++++++--
 gcc/cp/typeck.c                                    | 43 +++++++++--
 gcc/doc/invoke.texi                                | 22 ++++++
 gcc/flag-types.h                                   |  2 +
 gcc/ipa-inline.c                                   |  8 ++-
 gcc/opts.c                                         | 15 ++++
 gcc/sanitizer.def                                  |  4 ++
 .../c-c++-common/asan/pointer-compare-1.c          | 83 ++++++++++++++++++++++
 .../c-c++-common/asan/pointer-compare-2.c          | 76 ++++++++++++++++++++
 .../c-c++-common/asan/pointer-subtract-1.c         | 41 +++++++++++
 .../c-c++-common/asan/pointer-subtract-2.c         | 32 +++++++++
 libsanitizer/asan/asan_descriptions.cc             | 29 ++++++++
 libsanitizer/asan/asan_descriptions.h              |  5 ++
 libsanitizer/asan/asan_report.cc                   | 70 ++++++++++++++++--
 libsanitizer/asan/asan_thread.cc                   | 23 ++++++
 libsanitizer/asan/asan_thread.h                    |  3 +
 16 files changed, 471 insertions(+), 18 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c

diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index cb9c589e061..d623418288f 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -96,7 +96,7 @@ static tree lookup_field (tree, tree);
 static int convert_arguments (location_t, vec<location_t>, tree,
 			      vec<tree, va_gc> *, vec<tree, va_gc> *, tree,
 			      tree);
-static tree pointer_diff (location_t, tree, tree);
+static tree pointer_diff (location_t, tree, tree, tree *);
 static tree convert_for_assignment (location_t, location_t, tree, tree, tree,
 				    enum impl_conv, bool, tree, tree, int);
 static tree valid_compound_expr_initializer (tree, tree);
@@ -3779,10 +3779,11 @@ parser_build_binary_op (location_t location, enum tree_code code,
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
-pointer_diff (location_t loc, tree op0, tree op1)
+pointer_diff (location_t loc, tree op0, tree op1, tree *instrument_expr)
 {
   tree restype = ptrdiff_type_node;
   tree result, inttype;
@@ -3826,6 +3827,17 @@ pointer_diff (location_t loc, tree op0, tree op1)
     pedwarn (loc, OPT_Wpointer_arith,
 	     "pointer to a function used in subtraction");
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      gcc_assert (current_function_decl != NULL_TREE);
+
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction as integers;
      then drop through to build the divide operator.
      Do not do default conversions on the minus operator
@@ -11188,7 +11200,7 @@ build_binary_op (location_t location, enum tree_code code,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && comp_target_types (location, type0, type1))
 	{
-	  ret = pointer_diff (location, op0, op1);
+	  ret = pointer_diff (location, op0, op1, &instrument_expr);
 	  goto return_build_binary_op;
 	}
       /* Handle pointer minus int.  Just like pointer plus int.  */
@@ -11738,6 +11750,19 @@ build_binary_op (location_t location, enum tree_code code,
 	  result_type = type1;
 	  pedwarn (location, 0, "comparison between pointer and integer");
 	}
+
+      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
+	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	{
+	  gcc_assert (current_function_decl != NULL_TREE);
+
+	  op0 = save_expr (op0);
+	  op1 = save_expr (op1);
+
+	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
+	}
+
       if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
 	   || truth_value_p (TREE_CODE (orig_op0)))
 	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 08b2ae555e6..25087934910 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -54,7 +54,7 @@ static tree rationalize_conditional_expr (enum tree_code, tree,
 static int comp_ptr_ttypes_real (tree, tree, int);
 static bool comp_except_types (tree, tree, bool);
 static bool comp_array_types (const_tree, const_tree, bool);
-static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t);
+static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t, tree *);
 static tree get_delta_difference (tree, tree, bool, bool, tsubst_flags_t);
 static void casts_away_constness_r (tree *, tree *, tsubst_flags_t);
 static bool casts_away_constness (tree, tree, tsubst_flags_t);
@@ -4318,8 +4318,16 @@ cp_build_binary_op (location_t location,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (type0),
 							TREE_TYPE (type1)))
-	return pointer_diff (location, op0, op1,
-			     common_pointer_type (type0, type1), complain);
+	{
+	  result = pointer_diff (location, op0, op1,
+				 common_pointer_type (type0, type1), complain,
+				 &instrument_expr);
+	  if (instrument_expr != NULL)
+	    result = build2 (COMPOUND_EXPR, TREE_TYPE (result),
+			     instrument_expr, result);
+
+	  return result;
+	}
       /* In all other cases except pointer - int, the usual arithmetic
 	 rules apply.  */
       else if (!(code0 == POINTER_TYPE && code1 == INTEGER_TYPE))
@@ -5008,6 +5016,19 @@ cp_build_binary_op (location_t location,
           else
             return error_mark_node;
 	}
+
+      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
+	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	{
+	  gcc_assert (current_function_decl != NULL_TREE);
+
+	  op0 = save_expr (op0);
+	  op1 = save_expr (op1);
+
+	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
+	}
+
       break;
 
     case UNORDERED_EXPR:
@@ -5363,11 +5384,12 @@ cp_pointer_int_sum (location_t loc, enum tree_code resultcode, tree ptrop,
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
 pointer_diff (location_t loc, tree op0, tree op1, tree ptrtype,
-	      tsubst_flags_t complain)
+	      tsubst_flags_t complain, tree *instrument_expr)
 {
   tree result;
   tree restype = ptrdiff_type_node;
@@ -5401,6 +5423,17 @@ pointer_diff (location_t loc, tree op0, tree op1, tree ptrtype,
 	return error_mark_node;
     }
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      gcc_assert (current_function_decl != NULL_TREE);
+
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction as integers;
      then drop through to build the divide operator.  */
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index b358e09b45d..3698e8c5d11 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10960,6 +10960,28 @@ Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 1f439d35b07..74464651e00 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_VPTR = 1UL << 22,
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
+  SANITIZE_POINTER_COMPARE = 1UL << 25,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 26,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index dd46cb61362..d60432cded3 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -263,8 +263,12 @@ sanitize_attrs_match_for_inline_p (const_tree caller, const_tree callee)
   if (!caller || !callee)
     return true;
 
-  return sanitize_flags_p (SANITIZE_ADDRESS, caller)
-    == sanitize_flags_p (SANITIZE_ADDRESS, callee);
+  return ((sanitize_flags_p (SANITIZE_ADDRESS, caller)
+	   == sanitize_flags_p (SANITIZE_ADDRESS, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_COMPARE, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_COMPARE, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, callee)));
 }
 
 /* Used for flags where it is safe to inline when caller's value is
diff --git a/gcc/opts.c b/gcc/opts.c
index adf3d89851d..f39ead7e1cf 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -952,6 +952,19 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
   if (opts->x_dwarf_split_debug_info)
     opts->x_debug_generate_pub_sections = 2;
 
+  if ((opts->x_flag_sanitize
+       & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
+    {
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE)
+	error_at (loc,
+		  "%<-fsanitize=pointer-compare%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
+	error_at (loc,
+		  "%<-fsanitize=pointer-subtract%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+    }
+
   /* Userspace and kernel ASan conflict with each other.  */
   if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
       && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
@@ -1496,6 +1509,8 @@ const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
+  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 9d963f05c21..d06f68ba66e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c b/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..5f97d954a37
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
@@ -0,0 +1,83 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p, char *q)
+{
+  return p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+char small_global[7] = {};
+char large_global[5000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+  __builtin_free (heap1);
+  __builtin_free (heap2);
+
+  heap1 = (char *)__builtin_malloc(1024);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 1025);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1 + 1024, heap1 + 1025);
+  __builtin_free (heap1);
+
+  heap1 = (char *)__builtin_malloc(4096);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 4097);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, 0);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  char *p = &small_global[0];
+  foo (p, p); // OK
+  foo (p, p + 7); // OK
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p + 8);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p + 8);
+
+  p = &large_global[0];
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &small_global[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, 0);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&stack1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, 0);
+  return 1;
+}
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c b/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..4967064634f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
@@ -0,0 +1,76 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int foo(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+int baz(char *p, char *q)
+{
+  return p != 0 && p < q;
+}
+
+char global[8192] = {};
+char small_global[7] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = foo(p);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(1024);
+  bar(p, p + 1024);
+  bar(p + 1024, p + 1023);
+  bar(p + 1, p + 1023);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(4096);
+  bar(p, p + 4096);
+  bar(p + 10, p + 100);
+  bar(p + 1024, p + 4096);
+  bar(p + 4095, p + 4096);
+  bar(p + 4095, p + 4094);
+  bar(p + 100, p + 4096);
+  bar(p + 100, p + 4094);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[1]);
+  bar(&global[1], &global[2]);
+  bar(&global[2], &global[1]);
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[7000]);
+  bar(&global[500], &global[10]);
+  p = &global[0];
+  bar(p, p + 8192);
+  p = &global[8000];
+  bar(p, p + 192);
+
+  p = &small_global[0];
+  bar (p, p + 1);
+  bar (p, p + 7);
+  bar (p + 7, p + 1);
+  bar (p + 6, p + 7);
+  bar (p + 7, p + 7);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  baz(0, &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c b/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..10792264f2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int foo(char *p, char *q)
+{
+  return p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
+
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c b/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..17cf0e6711d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c
@@ -0,0 +1,32 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = bar(p, p - 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/libsanitizer/asan/asan_descriptions.cc b/libsanitizer/asan/asan_descriptions.cc
index 35d1619f2d9..8901772e0da 100644
--- a/libsanitizer/asan/asan_descriptions.cc
+++ b/libsanitizer/asan/asan_descriptions.cc
@@ -220,6 +220,15 @@ bool GetStackAddressInformation(uptr addr, uptr access_size,
   return true;
 }
 
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr)
+{
+  AsanThread *t = FindThreadByStackAddress(addr);
+  if (!t) return false;
+
+  *shadow_addr = t->GetStackFrameVariableBeginning(addr);
+  return true;
+}
+
 static void PrintAccessAndVarIntersection(const StackVarDescr &var, uptr addr,
                                           uptr access_size, uptr prev_var_end,
                                           uptr next_var_beg) {
@@ -330,6 +339,26 @@ void GlobalAddressDescription::Print(const char *bug_type) const {
   }
 }
 
+bool GlobalAddressDescription::PointsInsideASameVariable
+  (const GlobalAddressDescription &other) const
+{
+  if (size == 0 || other.size == 0)
+    return false;
+
+  for (unsigned i = 0; i < size; i++)
+    for (unsigned j = 0; j < other.size; j++) {
+      if (globals[i].beg == other.globals[j].beg &&
+	  globals[i].beg <= addr &&
+	  other.globals[j].beg <= other.addr &&
+	  (addr + access_size) < (globals[i].beg + globals[i].size) &&
+	  ((other.addr + other.access_size)
+	   < (other.globals[j].beg + other.globals[j].size)))
+	return true;
+    }
+
+  return false;
+}
+
 void StackAddressDescription::Print() const {
   Decorator d;
   char tname[128];
diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
index 584b9ba6491..35baec6f2f9 100644
--- a/libsanitizer/asan/asan_descriptions.h
+++ b/libsanitizer/asan/asan_descriptions.h
@@ -138,6 +138,7 @@ struct StackAddressDescription {
 
 bool GetStackAddressInformation(uptr addr, uptr access_size,
                                 StackAddressDescription *descr);
+bool GetStackVariableBeginning(uptr addr, uptr *shadow_addr);
 
 struct GlobalAddressDescription {
   uptr addr;
@@ -149,6 +150,10 @@ struct GlobalAddressDescription {
   u8 size;
 
   void Print(const char *bug_type = "") const;
+
+  // Return true when this descriptions points inside a same global variable
+  // as other. Descriptions can have different address within the variable
+  bool PointsInsideASameVariable (const GlobalAddressDescription &other) const;
 };
 
 bool GetGlobalAddressInformation(uptr addr, uptr access_size,
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 84d67646b40..aaf60b05032 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -344,14 +344,70 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
-    GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+
+  if (a1 == a2)
+    return;
+
+  uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
+  uptr left = a1 < a2 ? a1 : a2;
+  uptr right = a1 < a2 ? a2 : a1;
+  if (offset <= 2048) {
+    if (__asan_region_is_poisoned(left, offset) == 0)
+      return;
+    else
+      goto do_error;
+  }
+
+  {
+    uptr shadow_offset1, shadow_offset2;
+
+    {
+      ThreadRegistryLock l(&asanThreadRegistry());
+
+      // check whether left is a stack memory pointer
+      if (GetStackVariableBeginning(left, &shadow_offset1)) {
+	if (GetStackVariableBeginning(right - 1, &shadow_offset2) &&
+	    shadow_offset1 == shadow_offset2)
+	  return;
+	else
+	  goto do_error;
+      }
+    }
+
+    // check whether left is a heap memory address
+    HeapAddressDescription hdesc1, hdesc2;
+    if (GetHeapAddressInformation(left, 0, &hdesc1) &&
+	hdesc1.chunk_access.access_type == kAccessTypeInside) {
+      if (GetHeapAddressInformation(right, 0, &hdesc2) &&
+	  hdesc2.chunk_access.access_type == kAccessTypeInside &&
+	  (hdesc1.chunk_access.chunk_begin
+	   == hdesc2.chunk_access.chunk_begin))
+	return;
+      else
+	goto do_error;
+    }
+    // check whether left is an address of a global variable
+    GlobalAddressDescription gdesc1, gdesc2;
+    if (GetGlobalAddressInformation(left, 0, &gdesc1)) {
+      if (GetGlobalAddressInformation(right - 1, 0, &gdesc2) &&
+	  gdesc1.PointsInsideASameVariable (gdesc2))
+	return;
+    }
+    else {
+      ThreadRegistryLock l(&asanThreadRegistry());
+      if (GetStackVariableBeginning(right - 1, &shadow_offset2) ||
+	  GetHeapAddressInformation(right, 0, &hdesc2) ||
+	  GetGlobalAddressInformation(right - 1, 0, &gdesc2))
+	goto do_error;
+
+      /* At this point we know nothing about both a1 and a2 addresses.  */
+      return;
+    }
   }
+
+do_error:
+  GET_CALLER_PC_BP_SP;
+  ReportInvalidPointerPair(pc, bp, sp, a1, a2);
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
 
diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
index 818e1261400..eb6db52905d 100644
--- a/libsanitizer/asan/asan_thread.cc
+++ b/libsanitizer/asan/asan_thread.cc
@@ -322,6 +322,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
   return true;
 }
 
+uptr AsanThread::GetStackFrameVariableBeginning(uptr addr)
+{
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  }
+  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom &&
+	 (*shadow_ptr != kAsanStackLeftRedzoneMagic &&
+	  *shadow_ptr != kAsanStackMidRedzoneMagic &&
+	  *shadow_ptr != kAsanStackRightRedzoneMagic)) {
+    shadow_ptr--;
+  }
+
+  return (uptr)shadow_ptr;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
index c51a58ad0bb..edd1d815632 100644
--- a/libsanitizer/asan/asan_thread.h
+++ b/libsanitizer/asan/asan_thread.h
@@ -81,6 +81,9 @@ class AsanThread {
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
 
+  // Return beginning of a stack variable in shadow memory
+  uptr GetStackFrameVariableBeginning(uptr addr);
+
   bool AddrIsInStack(uptr addr);
 
   void DeleteFakeStack(int tid) {
Jakub Jelinek Oct. 16, 2017, 1:45 p.m. UTC | #17
On Mon, Oct 16, 2017 at 03:38:28PM +0200, Martin Liška wrote:
> --- a/libsanitizer/asan/asan_report.cc
> +++ b/libsanitizer/asan/asan_report.cc
> @@ -344,14 +344,70 @@ static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
>    if (!flags()->detect_invalid_pointer_pairs) return;
>    uptr a1 = reinterpret_cast<uptr>(p1);
>    uptr a2 = reinterpret_cast<uptr>(p2);
> -  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
> -  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
> -  bool valid1 = chunk1.IsAllocated();
> -  bool valid2 = chunk2.IsAllocated();
> -  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
> -    GET_CALLER_PC_BP_SP;
> -    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
> +
> +  if (a1 == a2)
> +    return;
> +
> +  uptr offset = a1 < a2 ? a2 - a1 : a1 - a2;
> +  uptr left = a1 < a2 ? a1 : a2;
> +  uptr right = a1 < a2 ? a2 : a1;
> +  if (offset <= 2048) {
> +    if (__asan_region_is_poisoned(left, offset) == 0)
> +      return;
> +    else
> +      goto do_error;
> +  }
> +
> +  {
> +    uptr shadow_offset1, shadow_offset2;

Some more nits.  This is C++ (but C99 would do as well), you don't need a new
scope for the above variables.  Just declare them without {} around and
extra indentation.

> +
> +    {
> +      ThreadRegistryLock l(&asanThreadRegistry());
> +
> +      // check whether left is a stack memory pointer
> +      if (GetStackVariableBeginning(left, &shadow_offset1)) {
> +	if (GetStackVariableBeginning(right - 1, &shadow_offset2) &&
> +	    shadow_offset1 == shadow_offset2)
> +	  return;
> +	else
> +	  goto do_error;
> +      }
> +    }
> +
> +    // check whether left is a heap memory address
> +    HeapAddressDescription hdesc1, hdesc2;
> +    if (GetHeapAddressInformation(left, 0, &hdesc1) &&
> +	hdesc1.chunk_access.access_type == kAccessTypeInside) {
> +      if (GetHeapAddressInformation(right, 0, &hdesc2) &&
> +	  hdesc2.chunk_access.access_type == kAccessTypeInside &&
> +	  (hdesc1.chunk_access.chunk_begin
> +	   == hdesc2.chunk_access.chunk_begin))
> +	return;
> +      else
> +	goto do_error;
> +    }
> +    // check whether left is an address of a global variable
> +    GlobalAddressDescription gdesc1, gdesc2;
> +    if (GetGlobalAddressInformation(left, 0, &gdesc1)) {
> +      if (GetGlobalAddressInformation(right - 1, 0, &gdesc2) &&
> +	  gdesc1.PointsInsideASameVariable (gdesc2))
> +	return;

I think it is better to use consistent coding, i.e. use else goto do_error;
here too.

> +    }
> +    else {

Without the else { here.

And do
      {
        ThreadRegistryLock l(&asanThreadRegistry());
        if (GetStackVariableBeginning(right - 1, &shadow_offset2))
          goto do_error;
      }
      if (GetHeapAddressInformation(right, 0, &hdesc2) ||
          GetGlobalAddressInformation(right - 1, 0, &gdesc2))
        goto do_error;

      /* At this point we know nothing about both a1 and a2 addresses.  */
      return;

so that the lock is held over GetStackVariableBeginning only.

	Jakub
Martin Liška Oct. 16, 2017, 8:39 p.m. UTC | #18
Hi.

All nits included in mainline review request I've just done:
https://reviews.llvm.org/D38971

Martin
Martin Liška Nov. 21, 2017, 11:59 a.m. UTC | #19
On 10/16/2017 10:39 PM, Martin Liška wrote:
> Hi.
> 
> All nits included in mainline review request I've just done:
> https://reviews.llvm.org/D38971
> 
> Martin

Hi.

There's updated version of patch where I added new test-cases and it's rebased
with latest version of libsanitizer changes. This is subject for libsanitizer review process.

Martin
From 100b723b9b7fb10dedb2154f30e1ebd6ef885ab4 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Wed, 8 Nov 2017 13:16:17 +0100
Subject: [PATCH] Add -fsanitize=pointer-{compare,subtract}.

gcc/ChangeLog:

2017-11-21  Martin Liska  <mliska@suse.cz>

	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* ipa-inline.c (sanitize_attrs_match_for_inline_p): Add handling
	of SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE): Likewise.
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.

gcc/c/ChangeLog:

2017-11-21  Martin Liska  <mliska@suse.cz>

	* c-typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(build_binary_op): Similar for pointer comparison.

gcc/cp/ChangeLog:

2017-11-21  Martin Liska  <mliska@suse.cz>

	* typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(cp_build_binary_op): Create compound expression if doing an
	instrumentation.

gcc/testsuite/ChangeLog:

2017-11-21  Martin Liska  <mliska@suse.cz>

	* c-c++-common/asan/pointer-compare-1.c: New test.
	* c-c++-common/asan/pointer-compare-2.c: New test.
	* c-c++-common/asan/pointer-subtract-1.c: New test.
	* c-c++-common/asan/pointer-subtract-2.c: New test.
	* c-c++-common/asan/pointer-subtract-3.c: New test.
	* c-c++-common/asan/pointer-subtract-4.c: New test.
---
 gcc/c/c-typeck.c                                   | 31 ++++++--
 gcc/cp/typeck.c                                    | 39 ++++++++--
 gcc/doc/invoke.texi                                | 22 ++++++
 gcc/flag-types.h                                   |  2 +
 gcc/ipa-inline.c                                   |  8 ++-
 gcc/opts.c                                         | 15 ++++
 gcc/sanitizer.def                                  |  4 ++
 .../c-c++-common/asan/pointer-compare-1.c          | 83 ++++++++++++++++++++++
 .../c-c++-common/asan/pointer-compare-2.c          | 76 ++++++++++++++++++++
 .../c-c++-common/asan/pointer-subtract-1.c         | 41 +++++++++++
 .../c-c++-common/asan/pointer-subtract-2.c         | 33 +++++++++
 .../c-c++-common/asan/pointer-subtract-3.c         | 40 +++++++++++
 .../c-c++-common/asan/pointer-subtract-4.c         | 40 +++++++++++
 libsanitizer/asan/asan_descriptions.cc             | 20 ++++++
 libsanitizer/asan/asan_descriptions.h              |  4 ++
 libsanitizer/asan/asan_report.cc                   | 53 ++++++++++++--
 libsanitizer/asan/asan_thread.cc                   | 25 ++++++-
 libsanitizer/asan/asan_thread.h                    |  3 +
 18 files changed, 521 insertions(+), 18 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-subtract-3.c
 create mode 100644 gcc/testsuite/c-c++-common/asan/pointer-subtract-4.c

diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index 4bdc48a9ea3..5dac9bdf08b 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -96,7 +96,7 @@ static tree lookup_field (tree, tree);
 static int convert_arguments (location_t, vec<location_t>, tree,
 			      vec<tree, va_gc> *, vec<tree, va_gc> *, tree,
 			      tree);
-static tree pointer_diff (location_t, tree, tree);
+static tree pointer_diff (location_t, tree, tree, tree *);
 static tree convert_for_assignment (location_t, location_t, tree, tree, tree,
 				    enum impl_conv, bool, tree, tree, int);
 static tree valid_compound_expr_initializer (tree, tree);
@@ -3778,10 +3778,11 @@ parser_build_binary_op (location_t location, enum tree_code code,
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
-pointer_diff (location_t loc, tree op0, tree op1)
+pointer_diff (location_t loc, tree op0, tree op1, tree *instrument_expr)
 {
   tree restype = ptrdiff_type_node;
   tree result, inttype;
@@ -3825,6 +3826,17 @@ pointer_diff (location_t loc, tree op0, tree op1)
     pedwarn (loc, OPT_Wpointer_arith,
 	     "pointer to a function used in subtraction");
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      gcc_assert (current_function_decl != NULL_TREE);
+
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction as integers;
      then drop through to build the divide operator.
      Do not do default conversions on the minus operator
@@ -11185,7 +11197,7 @@ build_binary_op (location_t location, enum tree_code code,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && comp_target_types (location, type0, type1))
 	{
-	  ret = pointer_diff (location, op0, op1);
+	  ret = pointer_diff (location, op0, op1, &instrument_expr);
 	  goto return_build_binary_op;
 	}
       /* Handle pointer minus int.  Just like pointer plus int.  */
@@ -11735,6 +11747,17 @@ build_binary_op (location_t location, enum tree_code code,
 	  result_type = type1;
 	  pedwarn (location, 0, "comparison between pointer and integer");
 	}
+
+      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
+	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	{
+	  op0 = save_expr (op0);
+	  op1 = save_expr (op1);
+
+	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
+	}
+
       if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
 	   || truth_value_p (TREE_CODE (orig_op0)))
 	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 9130c10f390..e2474267436 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -54,7 +54,7 @@ static tree rationalize_conditional_expr (enum tree_code, tree,
 static int comp_ptr_ttypes_real (tree, tree, int);
 static bool comp_except_types (tree, tree, bool);
 static bool comp_array_types (const_tree, const_tree, bool);
-static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t);
+static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t, tree *);
 static tree get_delta_difference (tree, tree, bool, bool, tsubst_flags_t);
 static void casts_away_constness_r (tree *, tree *, tsubst_flags_t);
 static bool casts_away_constness (tree, tree, tsubst_flags_t);
@@ -4318,8 +4318,16 @@ cp_build_binary_op (location_t location,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (type0),
 							TREE_TYPE (type1)))
-	return pointer_diff (location, op0, op1,
-			     common_pointer_type (type0, type1), complain);
+	{
+	  result = pointer_diff (location, op0, op1,
+				 common_pointer_type (type0, type1), complain,
+				 &instrument_expr);
+	  if (instrument_expr != NULL)
+	    result = build2 (COMPOUND_EXPR, TREE_TYPE (result),
+			     instrument_expr, result);
+
+	  return result;
+	}
       /* In all other cases except pointer - int, the usual arithmetic
 	 rules apply.  */
       else if (!(code0 == POINTER_TYPE && code1 == INTEGER_TYPE))
@@ -5008,6 +5016,17 @@ cp_build_binary_op (location_t location,
           else
             return error_mark_node;
 	}
+
+      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
+	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	{
+	  op0 = save_expr (op0);
+	  op1 = save_expr (op1);
+
+	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
+	}
+
       break;
 
     case UNORDERED_EXPR:
@@ -5363,11 +5382,12 @@ cp_pointer_int_sum (location_t loc, enum tree_code resultcode, tree ptrop,
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
 pointer_diff (location_t loc, tree op0, tree op1, tree ptrtype,
-	      tsubst_flags_t complain)
+	      tsubst_flags_t complain, tree *instrument_expr)
 {
   tree result;
   tree restype = ptrdiff_type_node;
@@ -5401,6 +5421,15 @@ pointer_diff (location_t loc, tree op0, tree op1, tree ptrtype,
 	return error_mark_node;
     }
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction as integers;
      then drop through to build the divide operator.  */
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 2ef88e081f9..4f6f6cd30ce 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10984,6 +10984,28 @@ Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 591b74457cd..3073c661872 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
   SANITIZE_BUILTIN = 1UL << 25,
+  SANITIZE_POINTER_COMPARE = 1UL << 26,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 27,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 687996876ce..6969b5365af 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -263,8 +263,12 @@ sanitize_attrs_match_for_inline_p (const_tree caller, const_tree callee)
   if (!caller || !callee)
     return true;
 
-  return sanitize_flags_p (SANITIZE_ADDRESS, caller)
-    == sanitize_flags_p (SANITIZE_ADDRESS, callee);
+  return ((sanitize_flags_p (SANITIZE_ADDRESS, caller)
+	   == sanitize_flags_p (SANITIZE_ADDRESS, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_COMPARE, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_COMPARE, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, callee)));
 }
 
 /* Used for flags where it is safe to inline when caller's value is
diff --git a/gcc/opts.c b/gcc/opts.c
index ac383d48ec1..c4dded7ff55 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -952,6 +952,19 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
   if (opts->x_dwarf_split_debug_info)
     opts->x_debug_generate_pub_sections = 2;
 
+  if ((opts->x_flag_sanitize
+       & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
+    {
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE)
+	error_at (loc,
+		  "%<-fsanitize=pointer-compare%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
+	error_at (loc,
+		  "%<-fsanitize=pointer-subtract%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+    }
+
   /* Userspace and kernel ASan conflict with each other.  */
   if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
       && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
@@ -1496,6 +1509,8 @@ const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
+  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 00e7ae031e6..7d224fa081e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c b/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..742a6a26f07
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-compare-1.c
@@ -0,0 +1,83 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" }
+// { dg-options "-fsanitize=address,pointer-compare -O0" }
+
+int foo(char *p, char *q)
+{
+  return p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+char small_global[7] = {};
+char large_global[5000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+  __builtin_free (heap1);
+  __builtin_free (heap2);
+
+  heap1 = (char *)__builtin_malloc(1024);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 1025);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1 + 1024, heap1 + 1025);
+  __builtin_free (heap1);
+
+  heap1 = (char *)__builtin_malloc(4096);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, heap1 + 4097);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (heap1, 0);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  char *p = &small_global[0];
+  foo (p, p); // OK
+  foo (p, p + 7); // OK
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p + 8);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p + 8);
+
+  p = &large_global[0];
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p - 1, p);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, p - 1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, &small_global[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo (p, 0);
+
+  /* Stack variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  char stack1, stack2;
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&stack1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, 0);
+  return 1;
+}
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c b/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..996c4812d41
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-compare-2.c
@@ -0,0 +1,76 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=address,pointer-compare -O0" }
+
+int foo(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+int baz(char *p, char *q)
+{
+  return p != 0 && p < q;
+}
+
+char global[8192] = {};
+char small_global[7] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = foo(p);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(1024);
+  bar(p, p + 1024);
+  bar(p + 1024, p + 1023);
+  bar(p + 1, p + 1023);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc(4096);
+  bar(p, p + 4096);
+  bar(p + 10, p + 100);
+  bar(p + 1024, p + 4096);
+  bar(p + 4095, p + 4096);
+  bar(p + 4095, p + 4094);
+  bar(p + 100, p + 4096);
+  bar(p + 100, p + 4094);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[1]);
+  bar(&global[1], &global[2]);
+  bar(&global[2], &global[1]);
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[7000]);
+  bar(&global[500], &global[10]);
+  p = &global[0];
+  bar(p, p + 8192);
+  p = &global[8000];
+  bar(p, p + 192);
+
+  p = &small_global[0];
+  bar (p, p + 1);
+  bar (p, p + 7);
+  bar (p + 7, p + 1);
+  bar (p + 6, p + 7);
+  bar (p + 7, p + 7);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  baz(0, &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c b/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..6d8adebf783
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c
@@ -0,0 +1,41 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=address,pointer-subtract -O0" }
+
+int foo(char *p, char *q)
+{
+  return p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc(42);
+  char *heap2 = (char *)__builtin_malloc(42);
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, heap2);
+
+  /* Global variables.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  char stack1, stack2;
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(&stack1, &stack2);
+
+  /* Mixtures.  */
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &stack1);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" }
+  foo(heap1, &global1[0]);
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  foo(&stack1, &global1[0]);
+  return 1;
+}
+
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c b/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..50bd62993fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c
@@ -0,0 +1,33 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" }
+// { dg-options "-fsanitize=address,pointer-subtract -O0" }
+
+int bar(char *p, char *q)
+{
+  return p <= q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc(42);
+  int r = bar(p, p - 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar(&global[0], &global[100]);
+  bar(&global[1000], &global[9000]);
+  bar(&global[500], &global[10]);
+  bar(&global[0], &global[10000]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar(&stack[0], &stack[100]);
+  bar(&stack[1000], &stack[9000]);
+  bar(&stack[500], &stack[10]);
+
+  return 0;
+}
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-subtract-3.c b/gcc/testsuite/c-c++-common/asan/pointer-subtract-3.c
new file mode 100644
index 00000000000..422a9a125e4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-subtract-3.c
@@ -0,0 +1,40 @@
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=1" }
+// { dg-options "-fsanitize=address,pointer-subtract -O0" }
+
+#include <unistd.h>
+#include <pthread.h>
+
+char *pointers[2];
+
+void *
+thread_main (void *n)
+{
+  char local;
+
+  unsigned long id = (unsigned long) n;
+  pointers[id] = &local;
+  sleep (1);
+
+  return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+  pthread_t threads[2];
+  pthread_create (&threads[0], 0, thread_main, (void *) 0);
+  pthread_create (&threads[1], 0, thread_main, (void *) 1);
+
+  do
+    {
+    }
+  while (pointers[0] == 0 || pointers[1] == 0);
+
+  unsigned r = pointers[0] - pointers[1];
+
+  pthread_join(threads[0], 0);
+  pthread_join(threads[1], 0);
+
+  return 0;
+}
diff --git a/gcc/testsuite/c-c++-common/asan/pointer-subtract-4.c b/gcc/testsuite/c-c++-common/asan/pointer-subtract-4.c
new file mode 100644
index 00000000000..f64f289c487
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/pointer-subtract-4.c
@@ -0,0 +1,40 @@
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=1" }
+// { dg-options "-fsanitize=address,pointer-subtract -O0" }
+
+#include <unistd.h>
+#include <pthread.h>
+
+char *pointer;
+
+void *
+thread_main (void *n)
+{
+  char local;
+  pointer = &local;
+  sleep (1);
+
+  return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+  pthread_t thread;
+  pthread_create (&thread, 0, thread_main, (void *) 0);
+
+  do
+    {
+    }
+  while (pointer == 0);
+
+  char local;
+  char *parent_pointer = &local;
+
+  // { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" }
+  unsigned r = parent_pointer - pointer;
+  pthread_join(thread, 0);
+
+  return 0;
+}
diff --git a/libsanitizer/asan/asan_descriptions.cc b/libsanitizer/asan/asan_descriptions.cc
index d46962adf27..c856a653742 100644
--- a/libsanitizer/asan/asan_descriptions.cc
+++ b/libsanitizer/asan/asan_descriptions.cc
@@ -333,6 +333,26 @@ void GlobalAddressDescription::Print(const char *bug_type) const {
   }
 }
 
+bool GlobalAddressDescription::PointsInsideTheSameVariable(
+    const GlobalAddressDescription &other) const {
+  if (size == 0 || other.size == 0) return false;
+
+  for (uptr i = 0; i < size; i++) {
+    const __asan_global &a = globals[i];
+    for (uptr j = 0; j < other.size; j++) {
+      const __asan_global &b = other.globals[j];
+      if (a.beg == b.beg &&
+          a.beg <= addr &&
+          b.beg <= other.addr &&
+          (addr + access_size) < (a.beg + a.size) &&
+          (other.addr + other.access_size) < (b.beg + b.size))
+        return true;
+    }
+  }
+
+  return false;
+}
+
 void StackAddressDescription::Print() const {
   Decorator d;
   char tname[128];
diff --git a/libsanitizer/asan/asan_descriptions.h b/libsanitizer/asan/asan_descriptions.h
index 0fbb531492a..12d9ac5f70b 100644
--- a/libsanitizer/asan/asan_descriptions.h
+++ b/libsanitizer/asan/asan_descriptions.h
@@ -143,6 +143,10 @@ struct GlobalAddressDescription {
   u8 size;
 
   void Print(const char *bug_type = "") const;
+
+  // Returns true when this descriptions points inside the same global variable
+  // as other. Descriptions can have different address within the variable
+  bool PointsInsideTheSameVariable(const GlobalAddressDescription &other) const;
 };
 
 bool GetGlobalAddressInformation(uptr addr, uptr access_size,
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 51bad6e45e3..1f19e726e1e 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -295,17 +295,58 @@ static NOINLINE void ReportInvalidPointerPair(uptr pc, uptr bp, uptr sp,
   in_report.ReportError(error);
 }
 
+static bool IsInvalidPointerPair(uptr a1, uptr a2) {
+  if (a1 == a2)
+    return false;
+
+  // 256B in shadow memory can be iterated quite fast
+  static const uptr kMaxOffset = 2048;
+
+  uptr left = a1 < a2 ? a1 : a2;
+  uptr right = a1 < a2 ? a2 : a1;
+  uptr offset = right - left;
+  if (offset <= kMaxOffset)
+    return __asan_region_is_poisoned(left, offset);
+
+  AsanThread *t = GetCurrentThread();
+
+  // check whether left is a stack memory pointer
+  if (uptr shadow_offset1 = t->GetStackVariableShadowStart(left)) {
+    uptr shadow_offset2 = t->GetStackVariableShadowStart(right);
+    return shadow_offset2 == 0 || shadow_offset1 != shadow_offset2;
+  }
+
+  // check whether left is a heap memory address
+  HeapAddressDescription hdesc1, hdesc2;
+  if (GetHeapAddressInformation(left, 0, &hdesc1) &&
+      hdesc1.chunk_access.access_type == kAccessTypeInside)
+    return !GetHeapAddressInformation(right, 0, &hdesc2) ||
+        hdesc2.chunk_access.access_type != kAccessTypeInside ||
+        hdesc1.chunk_access.chunk_begin != hdesc2.chunk_access.chunk_begin;
+
+  // check whether left is an address of a global variable
+  GlobalAddressDescription gdesc1, gdesc2;
+  if (GetGlobalAddressInformation(left, 0, &gdesc1))
+    return !GetGlobalAddressInformation(right - 1, 0, &gdesc2) ||
+        !gdesc1.PointsInsideTheSameVariable(gdesc2);
+
+  if (t->GetStackVariableShadowStart(right) ||
+      GetHeapAddressInformation(right, 0, &hdesc2) ||
+      GetGlobalAddressInformation(right - 1, 0, &gdesc2))
+    return true;
+
+  /* At this point we know nothing about both a1 and a2 addresses.  */
+  return false;
+}
+
 static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
+
+  if (IsInvalidPointerPair(a1, a2)) {
     GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+    ReportInvalidPointerPair(pc, bp, sp, a1, a2);
   }
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
diff --git a/libsanitizer/asan/asan_thread.cc b/libsanitizer/asan/asan_thread.cc
index d0fdf6e9847..178909a52b4 100644
--- a/libsanitizer/asan/asan_thread.cc
+++ b/libsanitizer/asan/asan_thread.cc
@@ -315,7 +315,7 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
     access->frame_descr = (const char *)((uptr*)bottom)[1];
     return true;
   }
-  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  uptr aligned_addr = RoundDownTo(addr, SANITIZER_WORDSIZE / 8);  // align addr.
   uptr mem_ptr = RoundDownTo(aligned_addr, SHADOW_GRANULARITY);
   u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
   u8 *shadow_bottom = (u8*)MemToShadow(bottom);
@@ -344,6 +344,29 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
   return true;
 }
 
+uptr AsanThread::GetStackVariableShadowStart(uptr addr) {
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  } else
+    return 0;
+
+  uptr aligned_addr = RoundDownTo(addr, SANITIZER_WORDSIZE / 8);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom &&
+         (*shadow_ptr != kAsanStackLeftRedzoneMagic &&
+          *shadow_ptr != kAsanStackMidRedzoneMagic &&
+          *shadow_ptr != kAsanStackRightRedzoneMagic))
+    shadow_ptr--;
+
+  return (uptr)shadow_ptr;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h
index f7a91f3e73b..3195edb9f6f 100644
--- a/libsanitizer/asan/asan_thread.h
+++ b/libsanitizer/asan/asan_thread.h
@@ -88,6 +88,9 @@ class AsanThread {
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
 
+  // Return beginning of a stack variable in shadow memory
+  uptr GetStackVariableShadowStart(uptr addr);
+
   bool AddrIsInStack(uptr addr);
 
   void DeleteFakeStack(int tid) {
Jakub Jelinek Dec. 5, 2017, 9:27 a.m. UTC | #20
On Tue, Nov 21, 2017 at 12:59:46PM +0100, Martin Liška wrote:
> On 10/16/2017 10:39 PM, Martin Liška wrote:
> > Hi.
> > 
> > All nits included in mainline review request I've just done:
> > https://reviews.llvm.org/D38971
> > 
> > Martin
> 
> Hi.
> 
> There's updated version of patch where I added new test-cases and it's rebased
> with latest version of libsanitizer changes. This is subject for libsanitizer review process.

A slightly modified version has been finally accepted upstream, here is the
updated patch with that final version cherry-picked, plus small changes to
make it apply after the POINTER_DIFF_EXPR changes, and a lot of testsuite
tweaks, so that we don't run it 7 times with -O0, but with different
optimization levels, cleanups etc.
The most important change I've done in the testsuite was pointer-subtract-2.c
used -fsanitize=address,pointer-subtract, but the function was actually
doing pointer comparison.  Guess that needs to be propagated upstream at
some point.  Another thing is that in pointer-compare-1.c where you test
p - 1, p and p, p - 1 on the global variables, we need to ensure there is
some other array before it, otherwise we run into the issue that there is no
red zone before the first global (and when optimizing, global objects seems
to be sorted by decreasing size).

Bootstrapped/regtested on x86_64-linux and i686-linux, committed to trunk.

2017-12-05  Martin Liska  <mliska@suse.cz>
	    Jakub Jelinek  <jakub@redhat.com>

gcc/
	* doc/invoke.texi: Document the options.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* ipa-inline.c (sanitize_attrs_match_for_inline_p): Add handling
	of SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
	* opts.c: Define new sanitizer options.
	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE): Likewise.
	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.
gcc/c/
	* c-typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(build_binary_op): Similar for pointer comparison.
gcc/cp/
	* typeck.c (pointer_diff): Add new argument and instrument
	pointer subtraction.
	(cp_build_binary_op): Create compound expression if doing an
	instrumentation.
gcc/testsuite/
	* c-c++-common/asan/pointer-compare-1.c: New test.
	* c-c++-common/asan/pointer-compare-2.c: New test.
	* c-c++-common/asan/pointer-subtract-1.c: New test.
	* c-c++-common/asan/pointer-subtract-2.c: New test.
	* c-c++-common/asan/pointer-subtract-3.c: New test.
	* c-c++-common/asan/pointer-subtract-4.c: New test.
libsanitizer/
	* asan/asan_descriptions.cc: Cherry-pick upstream r319668.
	* asan/asan_descriptions.h: Likewise.
	* asan/asan_report.cc: Likewise.
	* asan/asan_thread.cc: Likewise.
	* asan/asan_thread.h: Likewise.

--- gcc/c/c-typeck.c.jj	2017-12-01 19:42:09.504222186 +0100
+++ gcc/c/c-typeck.c	2017-12-04 22:41:50.680290866 +0100
@@ -95,7 +95,7 @@ static tree lookup_field (tree, tree);
 static int convert_arguments (location_t, vec<location_t>, tree,
 			      vec<tree, va_gc> *, vec<tree, va_gc> *, tree,
 			      tree);
-static tree pointer_diff (location_t, tree, tree);
+static tree pointer_diff (location_t, tree, tree, tree *);
 static tree convert_for_assignment (location_t, location_t, tree, tree, tree,
 				    enum impl_conv, bool, tree, tree, int);
 static tree valid_compound_expr_initializer (tree, tree);
@@ -3768,10 +3768,11 @@ parser_build_binary_op (location_t locat
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type ptrdiff_t.  */
+   The resulting tree has type ptrdiff_t.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
-pointer_diff (location_t loc, tree op0, tree op1)
+pointer_diff (location_t loc, tree op0, tree op1, tree *instrument_expr)
 {
   tree restype = ptrdiff_type_node;
   tree result, inttype;
@@ -3815,6 +3816,17 @@ pointer_diff (location_t loc, tree op0,
     pedwarn (loc, OPT_Wpointer_arith,
 	     "pointer to a function used in subtraction");
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      gcc_assert (current_function_decl != NULL_TREE);
+
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction, then build the divide operator
      and only convert at the very end.
      Do not do default conversions in case restype is a short type.  */
@@ -3825,8 +3837,8 @@ pointer_diff (location_t loc, tree op0,
      space, cast the pointers to some larger integer type and do the
      computations in that type.  */
   if (TYPE_PRECISION (inttype) > TYPE_PRECISION (TREE_TYPE (op0)))
-       op0 = build_binary_op (loc, MINUS_EXPR, convert (inttype, op0),
-			      convert (inttype, op1), false);
+    op0 = build_binary_op (loc, MINUS_EXPR, convert (inttype, op0),
+			   convert (inttype, op1), false);
   else
     op0 = build2_loc (loc, POINTER_DIFF_EXPR, inttype, op0, op1);
 
@@ -11113,7 +11125,7 @@ build_binary_op (location_t location, en
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && comp_target_types (location, type0, type1))
 	{
-	  ret = pointer_diff (location, op0, op1);
+	  ret = pointer_diff (location, op0, op1, &instrument_expr);
 	  goto return_build_binary_op;
 	}
       /* Handle pointer minus int.  Just like pointer plus int.  */
@@ -11663,6 +11675,17 @@ build_binary_op (location_t location, en
 	  result_type = type1;
 	  pedwarn (location, 0, "comparison between pointer and integer");
 	}
+
+      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
+	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	{
+	  op0 = save_expr (op0);
+	  op1 = save_expr (op1);
+
+	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
+	}
+
       if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
 	   || truth_value_p (TREE_CODE (orig_op0)))
 	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
--- gcc/cp/typeck.c.jj	2017-11-28 18:16:13.663825436 +0100
+++ gcc/cp/typeck.c	2017-12-04 22:43:29.597071458 +0100
@@ -54,7 +54,7 @@ static tree rationalize_conditional_expr
 static int comp_ptr_ttypes_real (tree, tree, int);
 static bool comp_except_types (tree, tree, bool);
 static bool comp_array_types (const_tree, const_tree, bool);
-static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t);
+static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t, tree *);
 static tree get_delta_difference (tree, tree, bool, bool, tsubst_flags_t);
 static void casts_away_constness_r (tree *, tree *, tsubst_flags_t);
 static bool casts_away_constness (tree, tree, tsubst_flags_t);
@@ -4329,8 +4329,16 @@ cp_build_binary_op (location_t location,
       if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
 	  && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (type0),
 							TREE_TYPE (type1)))
-	return pointer_diff (location, op0, op1,
-			     common_pointer_type (type0, type1), complain);
+	{
+	  result = pointer_diff (location, op0, op1,
+				 common_pointer_type (type0, type1), complain,
+				 &instrument_expr);
+	  if (instrument_expr != NULL)
+	    result = build2 (COMPOUND_EXPR, TREE_TYPE (result),
+			     instrument_expr, result);
+
+	  return result;
+	}
       /* In all other cases except pointer - int, the usual arithmetic
 	 rules apply.  */
       else if (!(code0 == POINTER_TYPE && code1 == INTEGER_TYPE))
@@ -5019,6 +5027,17 @@ cp_build_binary_op (location_t location,
           else
             return error_mark_node;
 	}
+
+      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
+	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
+	{
+	  op0 = save_expr (op0);
+	  op1 = save_expr (op1);
+
+	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
+	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
+	}
+
       break;
 
     case UNORDERED_EXPR:
@@ -5374,11 +5393,12 @@ cp_pointer_int_sum (location_t loc, enum
 }
 
 /* Return a tree for the difference of pointers OP0 and OP1.
-   The resulting tree has type int.  */
+   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
+   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
 
 static tree
 pointer_diff (location_t loc, tree op0, tree op1, tree ptrtype,
-	      tsubst_flags_t complain)
+	      tsubst_flags_t complain, tree *instrument_expr)
 {
   tree result, inttype;
   tree restype = ptrdiff_type_node;
@@ -5420,6 +5440,15 @@ pointer_diff (location_t loc, tree op0,
   else
     inttype = restype;
 
+  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
+    {
+      op0 = save_expr (op0);
+      op1 = save_expr (op1);
+
+      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
+      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
+    }
+
   /* First do the subtraction, then build the divide operator
      and only convert at the very end.
      Do not do default conversions in case restype is a short type.  */
--- gcc/doc/invoke.texi.jj	2017-12-04 20:10:29.232029972 +0100
+++ gcc/doc/invoke.texi	2017-12-04 22:38:28.151787561 +0100
@@ -11034,6 +11034,28 @@ Enable AddressSanitizer for Linux kernel
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=) with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
+@item -fsanitize=pointer-subtract
+@opindex fsanitize=pointer-subtract
+Instrument subtraction with pointer operands.
+The option must be combined with either @option{-fsanitize=kernel-address} or
+@option{-fsanitize=address}
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
--- gcc/flag-types.h.jj	2017-10-31 17:54:22.979383176 +0100
+++ gcc/flag-types.h	2017-12-04 22:38:28.151787561 +0100
@@ -246,6 +246,8 @@ enum sanitize_code {
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
   SANITIZE_BUILTIN = 1UL << 25,
+  SANITIZE_POINTER_COMPARE = 1UL << 26,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 27,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
--- gcc/ipa-inline.c.jj	2017-11-24 20:30:42.832102733 +0100
+++ gcc/ipa-inline.c	2017-12-04 22:38:28.152787548 +0100
@@ -260,8 +260,12 @@ sanitize_attrs_match_for_inline_p (const
   if (!caller || !callee)
     return true;
 
-  return sanitize_flags_p (SANITIZE_ADDRESS, caller)
-    == sanitize_flags_p (SANITIZE_ADDRESS, callee);
+  return ((sanitize_flags_p (SANITIZE_ADDRESS, caller)
+	   == sanitize_flags_p (SANITIZE_ADDRESS, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_COMPARE, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_COMPARE, callee))
+	  && (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, caller)
+	      == sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, callee)));
 }
 
 /* Used for flags where it is safe to inline when caller's value is
--- gcc/opts.c.jj	2017-11-21 23:17:33.245214916 +0100
+++ gcc/opts.c	2017-12-04 22:38:28.153787536 +0100
@@ -953,6 +953,19 @@ finish_options (struct gcc_options *opts
   if (opts->x_dwarf_split_debug_info)
     opts->x_debug_generate_pub_sections = 2;
 
+  if ((opts->x_flag_sanitize
+       & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
+    {
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE)
+	error_at (loc,
+		  "%<-fsanitize=pointer-compare%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+      if (opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
+	error_at (loc,
+		  "%<-fsanitize=pointer-subtract%> must be combined with "
+		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
+    }
+
   /* Userspace and kernel ASan conflict with each other.  */
   if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
       && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
@@ -1497,6 +1510,8 @@ const struct sanitizer_opts_s sanitizer_
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
+  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
--- gcc/sanitizer.def.jj	2017-11-21 23:17:34.019205449 +0100
+++ gcc/sanitizer.def	2017-12-04 22:38:28.153787536 +0100
@@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLO
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
--- gcc/testsuite/c-c++-common/asan/pointer-compare-1.c.jj	2017-12-04 22:38:28.154787524 +0100
+++ gcc/testsuite/c-c++-common/asan/pointer-compare-1.c	2017-12-05 10:09:02.459604949 +0100
@@ -0,0 +1,95 @@
+/* { dg-do run } */
+/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" } */
+/* { dg-options "-fsanitize=address,pointer-compare" } */
+
+volatile int v;
+
+__attribute__((noipa)) void
+foo (char *p, char *q)
+{
+  v = p > q;
+}
+
+char global1[100] = {}, global2[100] = {};
+char __attribute__((used)) smallest_global[5] = {};
+char small_global[7] = {};
+char __attribute__((used)) little_global[10] = {};
+char __attribute__((used)) medium_global[4000] = {};
+char large_global[5000] = {};
+char __attribute__((used)) largest_global[6000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc (42);
+  char *heap2 = (char *)__builtin_malloc (42);
+
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, heap2);
+  __builtin_free (heap1);
+  __builtin_free (heap2);
+
+  heap1 = (char *)__builtin_malloc (1024);
+  __asm ("" : "+g" (heap1));
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, heap1 + 1025);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1 + 1024, heap1 + 1025);
+  __builtin_free (heap1);
+
+  heap1 = (char *)__builtin_malloc (4096);
+  __asm ("" : "+g" (heap1));
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, heap1 + 4097);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, 0);
+
+  /* Global variables.  */
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (&global1[0], &global2[10]);
+
+  char *p = &small_global[0];
+  __asm ("" : "+g" (p));
+  foo (p, p); /* OK */
+  foo (p, p + 7); /* OK */
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p, p + 8);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p - 1, p);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p, p - 1);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p - 1, p + 8);
+
+  p = &large_global[0];
+  __asm ("" : "+g" (p));
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p - 1, p);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p, p - 1);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p, &global1[0]);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p, &small_global[0]);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (p, 0);
+
+  /* Stack variables.  */
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  char stack1, stack2;
+  foo (&stack1, &stack2);
+
+  /* Mixtures.  */
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, &stack1);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, &global1[0]);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (&stack1, &global1[0]);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" } */
+  foo (&stack1, 0);
+  __builtin_free (heap1);
+
+  return 0;
+}
--- gcc/testsuite/c-c++-common/asan/pointer-compare-2.c.jj	2017-12-04 22:38:28.154787524 +0100
+++ gcc/testsuite/c-c++-common/asan/pointer-compare-2.c	2017-12-05 09:30:24.709845766 +0100
@@ -0,0 +1,82 @@
+/* { dg-do run } */
+/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" } */
+/* { dg-options "-fsanitize=address,pointer-compare" } */
+
+volatile int v;
+
+int
+foo (char *p)
+{
+  char *p2 = p + 20;
+  v = p > p2;
+  return v;
+}
+
+void
+bar (char *p, char *q)
+{
+  v = p <= q;
+}
+
+void
+baz (char *p, char *q)
+{
+  v = (p != 0 && p < q);
+}
+
+char global[8192] = {};
+char small_global[7] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc (42);
+  int r = foo (p);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc (1024);
+  bar (p, p + 1024);
+  bar (p + 1024, p + 1023);
+  bar (p + 1, p + 1023);
+  __builtin_free (p);
+
+  p = (char *)__builtin_malloc (4096);
+  bar (p, p + 4096);
+  bar (p + 10, p + 100);
+  bar (p + 1024, p + 4096);
+  bar (p + 4095, p + 4096);
+  bar (p + 4095, p + 4094);
+  bar (p + 100, p + 4096);
+  bar (p + 100, p + 4094);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar (&global[0], &global[1]);
+  bar (&global[1], &global[2]);
+  bar (&global[2], &global[1]);
+  bar (&global[0], &global[100]);
+  bar (&global[1000], &global[7000]);
+  bar (&global[500], &global[10]);
+  p = &global[0];
+  bar (p, p + 8192);
+  p = &global[8000];
+  bar (p, p + 192);
+
+  p = &small_global[0];
+  bar (p, p + 1);
+  bar (p, p + 7);
+  bar (p + 7, p + 1);
+  bar (p + 6, p + 7);
+  bar (p + 7, p + 7);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar (&stack[0], &stack[100]);
+  bar (&stack[1000], &stack[9000]);
+  bar (&stack[500], &stack[10]);
+
+  baz (0, &stack[10]);
+
+  return 0;
+}
--- gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c.jj	2017-12-04 22:38:28.154787524 +0100
+++ gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c	2017-12-05 09:52:10.492373364 +0100
@@ -0,0 +1,45 @@
+/* { dg-do run } */
+/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" } */
+/* { dg-options "-fsanitize=address,pointer-subtract" } */
+
+volatile __PTRDIFF_TYPE__ v;
+
+__attribute__((noipa)) void
+foo (char *p, char *q)
+{
+  v = p - q;
+}
+
+char global1[100] = {}, global2[100] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *heap1 = (char *)__builtin_malloc (42);
+  char *heap2 = (char *)__builtin_malloc (42);
+
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, heap2);
+
+  /* Global variables.  */
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (&global1[0], &global2[10]);
+
+  /* Stack variables.  */
+  char stack1, stack2;
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (&stack1, &stack2);
+
+  /* Mixtures.  */
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, &stack1);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
+  foo (heap1, &global1[0]);
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" } */
+  foo (&stack1, &global1[0]);
+
+  __builtin_free (heap1);
+  __builtin_free (heap2);  
+  return 0;
+}
--- gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c.jj	2017-12-04 22:38:28.154787524 +0100
+++ gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c	2017-12-05 09:48:55.876826988 +0100
@@ -0,0 +1,37 @@
+/* { dg-do run } */
+/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" } */
+/* { dg-options "-fsanitize=address,pointer-subtract" } */
+
+volatile __PTRDIFF_TYPE__ v;
+
+void
+bar (char *p, char *q)
+{
+  v = q - p;
+  v = p - q;
+}
+
+char global[10000] = {};
+
+int
+main ()
+{
+  /* Heap allocated memory.  */
+  char *p = (char *)__builtin_malloc (42);
+  bar (p, p + 20);
+  __builtin_free (p);
+
+  /* Global variable.  */
+  bar (&global[0], &global[100]);
+  bar (&global[1000], &global[9000]);
+  bar (&global[500], &global[10]);
+  bar (&global[0], &global[10000]);
+
+  /* Stack variable.  */
+  char stack[10000];
+  bar (&stack[0], &stack[100]);
+  bar (&stack[1000], &stack[9000]);
+  bar (&stack[500], &stack[10]);
+
+  return 0;
+}
--- gcc/testsuite/c-c++-common/asan/pointer-subtract-3.c.jj	2017-12-04 22:38:28.155787511 +0100
+++ gcc/testsuite/c-c++-common/asan/pointer-subtract-3.c	2017-12-05 09:36:28.764246245 +0100
@@ -0,0 +1,43 @@
+/* { dg-do run { target pthread_h } } */
+/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=1" } */
+/* { dg-options "-fsanitize=address,pointer-subtract" } */
+/* { dg-additional-options "-pthread" { target pthread } } */
+
+#include <unistd.h>
+#include <pthread.h>
+
+char *pointers[2];
+pthread_barrier_t bar;
+
+void *
+thread_main (void *n)
+{
+  char local;
+
+  __UINTPTR_TYPE__ id = (__UINTPTR_TYPE__) n;
+  pointers[id] = &local;
+  pthread_barrier_wait (&bar);
+  pthread_barrier_wait (&bar);
+
+  return 0;
+}
+
+int
+main ()
+{
+  pthread_t threads[2];
+  pthread_barrier_init (&bar, NULL, 3);
+  pthread_create (&threads[0], NULL, thread_main, (void *) 0);
+  pthread_create (&threads[1], NULL, thread_main, (void *) 1);
+  pthread_barrier_wait (&bar);
+
+  /* This case is not handled yet.  */
+  volatile __PTRDIFF_TYPE__ r = pointers[0] - pointers[1];
+
+  pthread_barrier_wait (&bar);
+  pthread_join (threads[0], NULL);
+  pthread_join (threads[1], NULL);
+  pthread_barrier_destroy (&bar);
+
+  return 0;
+}
--- gcc/testsuite/c-c++-common/asan/pointer-subtract-4.c.jj	2017-12-04 22:38:28.155787511 +0100
+++ gcc/testsuite/c-c++-common/asan/pointer-subtract-4.c	2017-12-05 09:37:05.287785773 +0100
@@ -0,0 +1,43 @@
+/* { dg-do run { target pthread_h } } */
+/* { dg-shouldfail "asan" } */
+/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=1" } */
+/* { dg-options "-fsanitize=address,pointer-subtract" } */
+/* { dg-additional-options "-pthread" { target pthread } } */
+
+#include <unistd.h>
+#include <pthread.h>
+
+char *pointer;
+pthread_barrier_t bar;
+
+void *
+thread_main (void *n)
+{
+  char local;
+  (void) n;
+  pointer = &local;
+  pthread_barrier_wait (&bar);
+  pthread_barrier_wait (&bar);
+
+  return 0;
+}
+
+int
+main ()
+{
+  pthread_t thread;
+  pthread_barrier_init (&bar, NULL, 2);
+  pthread_create (&thread, NULL, thread_main, NULL);
+  pthread_barrier_wait (&bar);
+
+  char local;
+  char *parent_pointer = &local;
+
+  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" } */
+  volatile __PTRDIFF_TYPE__ r = parent_pointer - pointer;
+  pthread_barrier_wait (&bar);
+  pthread_join (thread, NULL);
+  pthread_barrier_destroy (&bar);
+
+  return 0;
+}
--- libsanitizer/asan/asan_descriptions.cc.jj	2017-11-21 23:18:20.440637693 +0100
+++ libsanitizer/asan/asan_descriptions.cc	2017-12-04 22:38:28.155787511 +0100
@@ -333,6 +333,26 @@ void GlobalAddressDescription::Print(con
   }
 }
 
+bool GlobalAddressDescription::PointsInsideTheSameVariable(
+    const GlobalAddressDescription &other) const {
+  if (size == 0 || other.size == 0) return false;
+
+  for (uptr i = 0; i < size; i++) {
+    const __asan_global &a = globals[i];
+    for (uptr j = 0; j < other.size; j++) {
+      const __asan_global &b = other.globals[j];
+      if (a.beg == b.beg &&
+          a.beg <= addr &&
+          b.beg <= other.addr &&
+          (addr + access_size) < (a.beg + a.size) &&
+          (other.addr + other.access_size) < (b.beg + b.size))
+        return true;
+    }
+  }
+
+  return false;
+}
+
 void StackAddressDescription::Print() const {
   Decorator d;
   char tname[128];
--- libsanitizer/asan/asan_descriptions.h.jj	2017-11-21 23:18:20.435637754 +0100
+++ libsanitizer/asan/asan_descriptions.h	2017-12-04 22:38:28.156787499 +0100
@@ -143,6 +143,10 @@ struct GlobalAddressDescription {
   u8 size;
 
   void Print(const char *bug_type = "") const;
+
+  // Returns true when this descriptions points inside the same global variable
+  // as other. Descriptions can have different address within the variable
+  bool PointsInsideTheSameVariable(const GlobalAddressDescription &other) const;
 };
 
 bool GetGlobalAddressInformation(uptr addr, uptr access_size,
--- libsanitizer/asan/asan_report.cc.jj	2017-11-21 23:18:20.435637754 +0100
+++ libsanitizer/asan/asan_report.cc	2017-12-04 22:38:28.156787499 +0100
@@ -295,17 +295,58 @@ static NOINLINE void ReportInvalidPointe
   in_report.ReportError(error);
 }
 
+static bool IsInvalidPointerPair(uptr a1, uptr a2) {
+  if (a1 == a2)
+    return false;
+
+  // 256B in shadow memory can be iterated quite fast
+  static const uptr kMaxOffset = 2048;
+
+  uptr left = a1 < a2 ? a1 : a2;
+  uptr right = a1 < a2 ? a2 : a1;
+  uptr offset = right - left;
+  if (offset <= kMaxOffset)
+    return __asan_region_is_poisoned(left, offset);
+
+  AsanThread *t = GetCurrentThread();
+
+  // check whether left is a stack memory pointer
+  if (uptr shadow_offset1 = t->GetStackVariableShadowStart(left)) {
+    uptr shadow_offset2 = t->GetStackVariableShadowStart(right);
+    return shadow_offset2 == 0 || shadow_offset1 != shadow_offset2;
+  }
+
+  // check whether left is a heap memory address
+  HeapAddressDescription hdesc1, hdesc2;
+  if (GetHeapAddressInformation(left, 0, &hdesc1) &&
+      hdesc1.chunk_access.access_type == kAccessTypeInside)
+    return !GetHeapAddressInformation(right, 0, &hdesc2) ||
+        hdesc2.chunk_access.access_type != kAccessTypeInside ||
+        hdesc1.chunk_access.chunk_begin != hdesc2.chunk_access.chunk_begin;
+
+  // check whether left is an address of a global variable
+  GlobalAddressDescription gdesc1, gdesc2;
+  if (GetGlobalAddressInformation(left, 0, &gdesc1))
+    return !GetGlobalAddressInformation(right - 1, 0, &gdesc2) ||
+        !gdesc1.PointsInsideTheSameVariable(gdesc2);
+
+  if (t->GetStackVariableShadowStart(right) ||
+      GetHeapAddressInformation(right, 0, &hdesc2) ||
+      GetGlobalAddressInformation(right - 1, 0, &gdesc2))
+    return true;
+
+  // At this point we know nothing about both a1 and a2 addresses.
+  return false;
+}
+
 static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
   if (!flags()->detect_invalid_pointer_pairs) return;
   uptr a1 = reinterpret_cast<uptr>(p1);
   uptr a2 = reinterpret_cast<uptr>(p2);
-  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
-  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
-  bool valid1 = chunk1.IsAllocated();
-  bool valid2 = chunk2.IsAllocated();
-  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
+
+  if (IsInvalidPointerPair(a1, a2)) {
     GET_CALLER_PC_BP_SP;
-    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
+    ReportInvalidPointerPair(pc, bp, sp, a1, a2);
   }
 }
 // ----------------------- Mac-specific reports ----------------- {{{1
--- libsanitizer/asan/asan_thread.cc.jj	2017-11-21 23:18:20.434637767 +0100
+++ libsanitizer/asan/asan_thread.cc	2017-12-04 22:38:28.156787499 +0100
@@ -315,7 +315,7 @@ bool AsanThread::GetStackFrameAccessByAd
     access->frame_descr = (const char *)((uptr*)bottom)[1];
     return true;
   }
-  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
+  uptr aligned_addr = RoundDownTo(addr, SANITIZER_WORDSIZE / 8);  // align addr.
   uptr mem_ptr = RoundDownTo(aligned_addr, SHADOW_GRANULARITY);
   u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
   u8 *shadow_bottom = (u8*)MemToShadow(bottom);
@@ -344,6 +344,29 @@ bool AsanThread::GetStackFrameAccessByAd
   return true;
 }
 
+uptr AsanThread::GetStackVariableShadowStart(uptr addr) {
+  uptr bottom = 0;
+  if (AddrIsInStack(addr)) {
+    bottom = stack_bottom();
+  } else if (has_fake_stack()) {
+    bottom = fake_stack()->AddrIsInFakeStack(addr);
+    CHECK(bottom);
+  } else
+    return 0;
+
+  uptr aligned_addr = RoundDownTo(addr, SANITIZER_WORDSIZE / 8);  // align addr.
+  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
+  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
+
+  while (shadow_ptr >= shadow_bottom &&
+         (*shadow_ptr != kAsanStackLeftRedzoneMagic &&
+          *shadow_ptr != kAsanStackMidRedzoneMagic &&
+          *shadow_ptr != kAsanStackRightRedzoneMagic))
+    shadow_ptr--;
+
+  return (uptr)shadow_ptr + 1;
+}
+
 bool AsanThread::AddrIsInStack(uptr addr) {
   const auto bounds = GetStackBounds();
   return addr >= bounds.bottom && addr < bounds.top;
--- libsanitizer/asan/asan_thread.h.jj	2017-11-21 23:18:20.435637754 +0100
+++ libsanitizer/asan/asan_thread.h	2017-12-04 22:38:28.157787487 +0100
@@ -88,6 +88,9 @@ class AsanThread {
   };
   bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
 
+  // Returns a pointer to the start of the stack variable's shadow memory.
+  uptr GetStackVariableShadowStart(uptr addr);
+
   bool AddrIsInStack(uptr addr);
 
   void DeleteFakeStack(int tid) {


	Jakub
Martin Liška Dec. 18, 2017, 8:23 a.m. UTC | #21
On 12/05/2017 10:27 AM, Jakub Jelinek wrote:
> On Tue, Nov 21, 2017 at 12:59:46PM +0100, Martin Liška wrote:
>> On 10/16/2017 10:39 PM, Martin Liška wrote:
>>> Hi.
>>>
>>> All nits included in mainline review request I've just done:
>>> https://reviews.llvm.org/D38971
>>>
>>> Martin
>>
>> Hi.
>>
>> There's updated version of patch where I added new test-cases and it's rebased
>> with latest version of libsanitizer changes. This is subject for libsanitizer review process.
> 
> A slightly modified version has been finally accepted upstream, here is the
> updated patch with that final version cherry-picked, plus small changes to
> make it apply after the POINTER_DIFF_EXPR changes, and a lot of testsuite
> tweaks, so that we don't run it 7 times with -O0, but with different
> optimization levels, cleanups etc.

Hi Jakub.

Thanks for finalizing the patch review and for the clean up you've done.

> The most important change I've done in the testsuite was pointer-subtract-2.c
> used -fsanitize=address,pointer-subtract, but the function was actually
> doing pointer comparison.  Guess that needs to be propagated upstream at
> some point.  Another thing is that in pointer-compare-1.c where you test
> p - 1, p and p, p - 1 on the global variables, we need to ensure there is
> some other array before it, otherwise we run into the issue that there is no
> red zone before the first global (and when optimizing, global objects seems
> to be sorted by decreasing size).

I'll add there minor issues to my TODO list and I'll create mainline patch review.

Martin

> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, committed to trunk.
> 
> 2017-12-05  Martin Liska  <mliska@suse.cz>
> 	    Jakub Jelinek  <jakub@redhat.com>
> 
> gcc/
> 	* doc/invoke.texi: Document the options.
> 	* flag-types.h (enum sanitize_code): Add
> 	SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
> 	* ipa-inline.c (sanitize_attrs_match_for_inline_p): Add handling
> 	of SANITIZE_POINTER_COMPARE and SANITIZE_POINTER_SUBTRACT.
> 	* opts.c: Define new sanitizer options.
> 	* sanitizer.def (BUILT_IN_ASAN_POINTER_COMPARE): Likewise.
> 	(BUILT_IN_ASAN_POINTER_SUBTRACT): Likewise.
> gcc/c/
> 	* c-typeck.c (pointer_diff): Add new argument and instrument
> 	pointer subtraction.
> 	(build_binary_op): Similar for pointer comparison.
> gcc/cp/
> 	* typeck.c (pointer_diff): Add new argument and instrument
> 	pointer subtraction.
> 	(cp_build_binary_op): Create compound expression if doing an
> 	instrumentation.
> gcc/testsuite/
> 	* c-c++-common/asan/pointer-compare-1.c: New test.
> 	* c-c++-common/asan/pointer-compare-2.c: New test.
> 	* c-c++-common/asan/pointer-subtract-1.c: New test.
> 	* c-c++-common/asan/pointer-subtract-2.c: New test.
> 	* c-c++-common/asan/pointer-subtract-3.c: New test.
> 	* c-c++-common/asan/pointer-subtract-4.c: New test.
> libsanitizer/
> 	* asan/asan_descriptions.cc: Cherry-pick upstream r319668.
> 	* asan/asan_descriptions.h: Likewise.
> 	* asan/asan_report.cc: Likewise.
> 	* asan/asan_thread.cc: Likewise.
> 	* asan/asan_thread.h: Likewise.
> 
> --- gcc/c/c-typeck.c.jj	2017-12-01 19:42:09.504222186 +0100
> +++ gcc/c/c-typeck.c	2017-12-04 22:41:50.680290866 +0100
> @@ -95,7 +95,7 @@ static tree lookup_field (tree, tree);
>  static int convert_arguments (location_t, vec<location_t>, tree,
>  			      vec<tree, va_gc> *, vec<tree, va_gc> *, tree,
>  			      tree);
> -static tree pointer_diff (location_t, tree, tree);
> +static tree pointer_diff (location_t, tree, tree, tree *);
>  static tree convert_for_assignment (location_t, location_t, tree, tree, tree,
>  				    enum impl_conv, bool, tree, tree, int);
>  static tree valid_compound_expr_initializer (tree, tree);
> @@ -3768,10 +3768,11 @@ parser_build_binary_op (location_t locat
>  }
>  
>  /* Return a tree for the difference of pointers OP0 and OP1.
> -   The resulting tree has type ptrdiff_t.  */
> +   The resulting tree has type ptrdiff_t.  If POINTER_SUBTRACT sanitization is
> +   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
>  
>  static tree
> -pointer_diff (location_t loc, tree op0, tree op1)
> +pointer_diff (location_t loc, tree op0, tree op1, tree *instrument_expr)
>  {
>    tree restype = ptrdiff_type_node;
>    tree result, inttype;
> @@ -3815,6 +3816,17 @@ pointer_diff (location_t loc, tree op0,
>      pedwarn (loc, OPT_Wpointer_arith,
>  	     "pointer to a function used in subtraction");
>  
> +  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
> +    {
> +      gcc_assert (current_function_decl != NULL_TREE);
> +
> +      op0 = save_expr (op0);
> +      op1 = save_expr (op1);
> +
> +      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
> +      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
> +    }
> +
>    /* First do the subtraction, then build the divide operator
>       and only convert at the very end.
>       Do not do default conversions in case restype is a short type.  */
> @@ -3825,8 +3837,8 @@ pointer_diff (location_t loc, tree op0,
>       space, cast the pointers to some larger integer type and do the
>       computations in that type.  */
>    if (TYPE_PRECISION (inttype) > TYPE_PRECISION (TREE_TYPE (op0)))
> -       op0 = build_binary_op (loc, MINUS_EXPR, convert (inttype, op0),
> -			      convert (inttype, op1), false);
> +    op0 = build_binary_op (loc, MINUS_EXPR, convert (inttype, op0),
> +			   convert (inttype, op1), false);
>    else
>      op0 = build2_loc (loc, POINTER_DIFF_EXPR, inttype, op0, op1);
>  
> @@ -11113,7 +11125,7 @@ build_binary_op (location_t location, en
>        if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
>  	  && comp_target_types (location, type0, type1))
>  	{
> -	  ret = pointer_diff (location, op0, op1);
> +	  ret = pointer_diff (location, op0, op1, &instrument_expr);
>  	  goto return_build_binary_op;
>  	}
>        /* Handle pointer minus int.  Just like pointer plus int.  */
> @@ -11663,6 +11675,17 @@ build_binary_op (location_t location, en
>  	  result_type = type1;
>  	  pedwarn (location, 0, "comparison between pointer and integer");
>  	}
> +
> +      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
> +	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
> +	{
> +	  op0 = save_expr (op0);
> +	  op1 = save_expr (op1);
> +
> +	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
> +	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
> +	}
> +
>        if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE
>  	   || truth_value_p (TREE_CODE (orig_op0)))
>  	  ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE
> --- gcc/cp/typeck.c.jj	2017-11-28 18:16:13.663825436 +0100
> +++ gcc/cp/typeck.c	2017-12-04 22:43:29.597071458 +0100
> @@ -54,7 +54,7 @@ static tree rationalize_conditional_expr
>  static int comp_ptr_ttypes_real (tree, tree, int);
>  static bool comp_except_types (tree, tree, bool);
>  static bool comp_array_types (const_tree, const_tree, bool);
> -static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t);
> +static tree pointer_diff (location_t, tree, tree, tree, tsubst_flags_t, tree *);
>  static tree get_delta_difference (tree, tree, bool, bool, tsubst_flags_t);
>  static void casts_away_constness_r (tree *, tree *, tsubst_flags_t);
>  static bool casts_away_constness (tree, tree, tsubst_flags_t);
> @@ -4329,8 +4329,16 @@ cp_build_binary_op (location_t location,
>        if (code0 == POINTER_TYPE && code1 == POINTER_TYPE
>  	  && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (type0),
>  							TREE_TYPE (type1)))
> -	return pointer_diff (location, op0, op1,
> -			     common_pointer_type (type0, type1), complain);
> +	{
> +	  result = pointer_diff (location, op0, op1,
> +				 common_pointer_type (type0, type1), complain,
> +				 &instrument_expr);
> +	  if (instrument_expr != NULL)
> +	    result = build2 (COMPOUND_EXPR, TREE_TYPE (result),
> +			     instrument_expr, result);
> +
> +	  return result;
> +	}
>        /* In all other cases except pointer - int, the usual arithmetic
>  	 rules apply.  */
>        else if (!(code0 == POINTER_TYPE && code1 == INTEGER_TYPE))
> @@ -5019,6 +5027,17 @@ cp_build_binary_op (location_t location,
>            else
>              return error_mark_node;
>  	}
> +
> +      if ((code0 == POINTER_TYPE || code1 == POINTER_TYPE)
> +	  && sanitize_flags_p (SANITIZE_POINTER_COMPARE))
> +	{
> +	  op0 = save_expr (op0);
> +	  op1 = save_expr (op1);
> +
> +	  tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_COMPARE);
> +	  instrument_expr = build_call_expr_loc (location, tt, 2, op0, op1);
> +	}
> +
>        break;
>  
>      case UNORDERED_EXPR:
> @@ -5374,11 +5393,12 @@ cp_pointer_int_sum (location_t loc, enum
>  }
>  
>  /* Return a tree for the difference of pointers OP0 and OP1.
> -   The resulting tree has type int.  */
> +   The resulting tree has type int.  If POINTER_SUBTRACT sanitization is
> +   enabled, assign to INSTRUMENT_EXPR call to libsanitizer.  */
>  
>  static tree
>  pointer_diff (location_t loc, tree op0, tree op1, tree ptrtype,
> -	      tsubst_flags_t complain)
> +	      tsubst_flags_t complain, tree *instrument_expr)
>  {
>    tree result, inttype;
>    tree restype = ptrdiff_type_node;
> @@ -5420,6 +5440,15 @@ pointer_diff (location_t loc, tree op0,
>    else
>      inttype = restype;
>  
> +  if (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT))
> +    {
> +      op0 = save_expr (op0);
> +      op1 = save_expr (op1);
> +
> +      tree tt = builtin_decl_explicit (BUILT_IN_ASAN_POINTER_SUBTRACT);
> +      *instrument_expr = build_call_expr_loc (loc, tt, 2, op0, op1);
> +    }
> +
>    /* First do the subtraction, then build the divide operator
>       and only convert at the very end.
>       Do not do default conversions in case restype is a short type.  */
> --- gcc/doc/invoke.texi.jj	2017-12-04 20:10:29.232029972 +0100
> +++ gcc/doc/invoke.texi	2017-12-04 22:38:28.151787561 +0100
> @@ -11034,6 +11034,28 @@ Enable AddressSanitizer for Linux kernel
>  See @uref{https://github.com/google/kasan/wiki} for more details.
>  The option cannot be combined with @option{-fcheck-pointer-bounds}.
>  
> +@item -fsanitize=pointer-compare
> +@opindex fsanitize=pointer-compare
> +Instrument comparison operation (<, <=, >, >=) with pointer operands.
> +The option must be combined with either @option{-fsanitize=kernel-address} or
> +@option{-fsanitize=address}
> +The option cannot be combined with @option{-fsanitize=thread}
> +and/or @option{-fcheck-pointer-bounds}.
> +Note: By default the check is disabled at run time.  To enable it,
> +add @code{detect_invalid_pointer_pairs=1} to the environment variable
> +@env{ASAN_OPTIONS}.
> +
> +@item -fsanitize=pointer-subtract
> +@opindex fsanitize=pointer-subtract
> +Instrument subtraction with pointer operands.
> +The option must be combined with either @option{-fsanitize=kernel-address} or
> +@option{-fsanitize=address}
> +The option cannot be combined with @option{-fsanitize=thread}
> +and/or @option{-fcheck-pointer-bounds}.
> +Note: By default the check is disabled at run time.  To enable it,
> +add @code{detect_invalid_pointer_pairs=1} to the environment variable
> +@env{ASAN_OPTIONS}.
> +
>  @item -fsanitize=thread
>  @opindex fsanitize=thread
>  Enable ThreadSanitizer, a fast data race detector.
> --- gcc/flag-types.h.jj	2017-10-31 17:54:22.979383176 +0100
> +++ gcc/flag-types.h	2017-12-04 22:38:28.151787561 +0100
> @@ -246,6 +246,8 @@ enum sanitize_code {
>    SANITIZE_BOUNDS_STRICT = 1UL << 23,
>    SANITIZE_POINTER_OVERFLOW = 1UL << 24,
>    SANITIZE_BUILTIN = 1UL << 25,
> +  SANITIZE_POINTER_COMPARE = 1UL << 26,
> +  SANITIZE_POINTER_SUBTRACT = 1UL << 27,
>    SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
>    SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
>  		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
> --- gcc/ipa-inline.c.jj	2017-11-24 20:30:42.832102733 +0100
> +++ gcc/ipa-inline.c	2017-12-04 22:38:28.152787548 +0100
> @@ -260,8 +260,12 @@ sanitize_attrs_match_for_inline_p (const
>    if (!caller || !callee)
>      return true;
>  
> -  return sanitize_flags_p (SANITIZE_ADDRESS, caller)
> -    == sanitize_flags_p (SANITIZE_ADDRESS, callee);
> +  return ((sanitize_flags_p (SANITIZE_ADDRESS, caller)
> +	   == sanitize_flags_p (SANITIZE_ADDRESS, callee))
> +	  && (sanitize_flags_p (SANITIZE_POINTER_COMPARE, caller)
> +	      == sanitize_flags_p (SANITIZE_POINTER_COMPARE, callee))
> +	  && (sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, caller)
> +	      == sanitize_flags_p (SANITIZE_POINTER_SUBTRACT, callee)));
>  }
>  
>  /* Used for flags where it is safe to inline when caller's value is
> --- gcc/opts.c.jj	2017-11-21 23:17:33.245214916 +0100
> +++ gcc/opts.c	2017-12-04 22:38:28.153787536 +0100
> @@ -953,6 +953,19 @@ finish_options (struct gcc_options *opts
>    if (opts->x_dwarf_split_debug_info)
>      opts->x_debug_generate_pub_sections = 2;
>  
> +  if ((opts->x_flag_sanitize
> +       & (SANITIZE_USER_ADDRESS | SANITIZE_KERNEL_ADDRESS)) == 0)
> +    {
> +      if (opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE)
> +	error_at (loc,
> +		  "%<-fsanitize=pointer-compare%> must be combined with "
> +		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
> +      if (opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
> +	error_at (loc,
> +		  "%<-fsanitize=pointer-subtract%> must be combined with "
> +		  "%<-fsanitize=address%> or %<-fsanitize=kernel-address%>");
> +    }
> +
>    /* Userspace and kernel ASan conflict with each other.  */
>    if ((opts->x_flag_sanitize & SANITIZE_USER_ADDRESS)
>        && (opts->x_flag_sanitize & SANITIZE_KERNEL_ADDRESS))
> @@ -1497,6 +1510,8 @@ const struct sanitizer_opts_s sanitizer_
>    SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
>    SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
>  		 true),
> +  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
> +  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
>    SANITIZER_OPT (thread, SANITIZE_THREAD, false),
>    SANITIZER_OPT (leak, SANITIZE_LEAK, false),
>    SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
> --- gcc/sanitizer.def.jj	2017-11-21 23:17:34.019205449 +0100
> +++ gcc/sanitizer.def	2017-12-04 22:38:28.153787536 +0100
> @@ -175,6 +175,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLO
>  		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
>  DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
>  		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
> +DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
> +		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
> +DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
> +		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
>  
>  /* Thread Sanitizer */
>  DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
> --- gcc/testsuite/c-c++-common/asan/pointer-compare-1.c.jj	2017-12-04 22:38:28.154787524 +0100
> +++ gcc/testsuite/c-c++-common/asan/pointer-compare-1.c	2017-12-05 10:09:02.459604949 +0100
> @@ -0,0 +1,95 @@
> +/* { dg-do run } */
> +/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=0" } */
> +/* { dg-options "-fsanitize=address,pointer-compare" } */
> +
> +volatile int v;
> +
> +__attribute__((noipa)) void
> +foo (char *p, char *q)
> +{
> +  v = p > q;
> +}
> +
> +char global1[100] = {}, global2[100] = {};
> +char __attribute__((used)) smallest_global[5] = {};
> +char small_global[7] = {};
> +char __attribute__((used)) little_global[10] = {};
> +char __attribute__((used)) medium_global[4000] = {};
> +char large_global[5000] = {};
> +char __attribute__((used)) largest_global[6000] = {};
> +
> +int
> +main ()
> +{
> +  /* Heap allocated memory.  */
> +  char *heap1 = (char *)__builtin_malloc (42);
> +  char *heap2 = (char *)__builtin_malloc (42);
> +
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, heap2);
> +  __builtin_free (heap1);
> +  __builtin_free (heap2);
> +
> +  heap1 = (char *)__builtin_malloc (1024);
> +  __asm ("" : "+g" (heap1));
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, heap1 + 1025);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1 + 1024, heap1 + 1025);
> +  __builtin_free (heap1);
> +
> +  heap1 = (char *)__builtin_malloc (4096);
> +  __asm ("" : "+g" (heap1));
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, heap1 + 4097);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, 0);
> +
> +  /* Global variables.  */
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (&global1[0], &global2[10]);
> +
> +  char *p = &small_global[0];
> +  __asm ("" : "+g" (p));
> +  foo (p, p); /* OK */
> +  foo (p, p + 7); /* OK */
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p, p + 8);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p - 1, p);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p, p - 1);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p - 1, p + 8);
> +
> +  p = &large_global[0];
> +  __asm ("" : "+g" (p));
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p - 1, p);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p, p - 1);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p, &global1[0]);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p, &small_global[0]);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (p, 0);
> +
> +  /* Stack variables.  */
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  char stack1, stack2;
> +  foo (&stack1, &stack2);
> +
> +  /* Mixtures.  */
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, &stack1);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, &global1[0]);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (&stack1, &global1[0]);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" } */
> +  foo (&stack1, 0);
> +  __builtin_free (heap1);
> +
> +  return 0;
> +}
> --- gcc/testsuite/c-c++-common/asan/pointer-compare-2.c.jj	2017-12-04 22:38:28.154787524 +0100
> +++ gcc/testsuite/c-c++-common/asan/pointer-compare-2.c	2017-12-05 09:30:24.709845766 +0100
> @@ -0,0 +1,82 @@
> +/* { dg-do run } */
> +/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" } */
> +/* { dg-options "-fsanitize=address,pointer-compare" } */
> +
> +volatile int v;
> +
> +int
> +foo (char *p)
> +{
> +  char *p2 = p + 20;
> +  v = p > p2;
> +  return v;
> +}
> +
> +void
> +bar (char *p, char *q)
> +{
> +  v = p <= q;
> +}
> +
> +void
> +baz (char *p, char *q)
> +{
> +  v = (p != 0 && p < q);
> +}
> +
> +char global[8192] = {};
> +char small_global[7] = {};
> +
> +int
> +main ()
> +{
> +  /* Heap allocated memory.  */
> +  char *p = (char *)__builtin_malloc (42);
> +  int r = foo (p);
> +  __builtin_free (p);
> +
> +  p = (char *)__builtin_malloc (1024);
> +  bar (p, p + 1024);
> +  bar (p + 1024, p + 1023);
> +  bar (p + 1, p + 1023);
> +  __builtin_free (p);
> +
> +  p = (char *)__builtin_malloc (4096);
> +  bar (p, p + 4096);
> +  bar (p + 10, p + 100);
> +  bar (p + 1024, p + 4096);
> +  bar (p + 4095, p + 4096);
> +  bar (p + 4095, p + 4094);
> +  bar (p + 100, p + 4096);
> +  bar (p + 100, p + 4094);
> +  __builtin_free (p);
> +
> +  /* Global variable.  */
> +  bar (&global[0], &global[1]);
> +  bar (&global[1], &global[2]);
> +  bar (&global[2], &global[1]);
> +  bar (&global[0], &global[100]);
> +  bar (&global[1000], &global[7000]);
> +  bar (&global[500], &global[10]);
> +  p = &global[0];
> +  bar (p, p + 8192);
> +  p = &global[8000];
> +  bar (p, p + 192);
> +
> +  p = &small_global[0];
> +  bar (p, p + 1);
> +  bar (p, p + 7);
> +  bar (p + 7, p + 1);
> +  bar (p + 6, p + 7);
> +  bar (p + 7, p + 7);
> +
> +  /* Stack variable.  */
> +  char stack[10000];
> +  bar (&stack[0], &stack[100]);
> +  bar (&stack[1000], &stack[9000]);
> +  bar (&stack[500], &stack[10]);
> +
> +  baz (0, &stack[10]);
> +
> +  return 0;
> +}
> --- gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c.jj	2017-12-04 22:38:28.154787524 +0100
> +++ gcc/testsuite/c-c++-common/asan/pointer-subtract-1.c	2017-12-05 09:52:10.492373364 +0100
> @@ -0,0 +1,45 @@
> +/* { dg-do run } */
> +/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" } */
> +/* { dg-options "-fsanitize=address,pointer-subtract" } */
> +
> +volatile __PTRDIFF_TYPE__ v;
> +
> +__attribute__((noipa)) void
> +foo (char *p, char *q)
> +{
> +  v = p - q;
> +}
> +
> +char global1[100] = {}, global2[100] = {};
> +
> +int
> +main ()
> +{
> +  /* Heap allocated memory.  */
> +  char *heap1 = (char *)__builtin_malloc (42);
> +  char *heap2 = (char *)__builtin_malloc (42);
> +
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, heap2);
> +
> +  /* Global variables.  */
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (&global1[0], &global2[10]);
> +
> +  /* Stack variables.  */
> +  char stack1, stack2;
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (&stack1, &stack2);
> +
> +  /* Mixtures.  */
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, &stack1);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*" } */
> +  foo (heap1, &global1[0]);
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" } */
> +  foo (&stack1, &global1[0]);
> +
> +  __builtin_free (heap1);
> +  __builtin_free (heap2);  
> +  return 0;
> +}
> --- gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c.jj	2017-12-04 22:38:28.154787524 +0100
> +++ gcc/testsuite/c-c++-common/asan/pointer-subtract-2.c	2017-12-05 09:48:55.876826988 +0100
> @@ -0,0 +1,37 @@
> +/* { dg-do run } */
> +/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=1" } */
> +/* { dg-options "-fsanitize=address,pointer-subtract" } */
> +
> +volatile __PTRDIFF_TYPE__ v;
> +
> +void
> +bar (char *p, char *q)
> +{
> +  v = q - p;
> +  v = p - q;
> +}
> +
> +char global[10000] = {};
> +
> +int
> +main ()
> +{
> +  /* Heap allocated memory.  */
> +  char *p = (char *)__builtin_malloc (42);
> +  bar (p, p + 20);
> +  __builtin_free (p);
> +
> +  /* Global variable.  */
> +  bar (&global[0], &global[100]);
> +  bar (&global[1000], &global[9000]);
> +  bar (&global[500], &global[10]);
> +  bar (&global[0], &global[10000]);
> +
> +  /* Stack variable.  */
> +  char stack[10000];
> +  bar (&stack[0], &stack[100]);
> +  bar (&stack[1000], &stack[9000]);
> +  bar (&stack[500], &stack[10]);
> +
> +  return 0;
> +}
> --- gcc/testsuite/c-c++-common/asan/pointer-subtract-3.c.jj	2017-12-04 22:38:28.155787511 +0100
> +++ gcc/testsuite/c-c++-common/asan/pointer-subtract-3.c	2017-12-05 09:36:28.764246245 +0100
> @@ -0,0 +1,43 @@
> +/* { dg-do run { target pthread_h } } */
> +/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=1" } */
> +/* { dg-options "-fsanitize=address,pointer-subtract" } */
> +/* { dg-additional-options "-pthread" { target pthread } } */
> +
> +#include <unistd.h>
> +#include <pthread.h>
> +
> +char *pointers[2];
> +pthread_barrier_t bar;
> +
> +void *
> +thread_main (void *n)
> +{
> +  char local;
> +
> +  __UINTPTR_TYPE__ id = (__UINTPTR_TYPE__) n;
> +  pointers[id] = &local;
> +  pthread_barrier_wait (&bar);
> +  pthread_barrier_wait (&bar);
> +
> +  return 0;
> +}
> +
> +int
> +main ()
> +{
> +  pthread_t threads[2];
> +  pthread_barrier_init (&bar, NULL, 3);
> +  pthread_create (&threads[0], NULL, thread_main, (void *) 0);
> +  pthread_create (&threads[1], NULL, thread_main, (void *) 1);
> +  pthread_barrier_wait (&bar);
> +
> +  /* This case is not handled yet.  */
> +  volatile __PTRDIFF_TYPE__ r = pointers[0] - pointers[1];
> +
> +  pthread_barrier_wait (&bar);
> +  pthread_join (threads[0], NULL);
> +  pthread_join (threads[1], NULL);
> +  pthread_barrier_destroy (&bar);
> +
> +  return 0;
> +}
> --- gcc/testsuite/c-c++-common/asan/pointer-subtract-4.c.jj	2017-12-04 22:38:28.155787511 +0100
> +++ gcc/testsuite/c-c++-common/asan/pointer-subtract-4.c	2017-12-05 09:37:05.287785773 +0100
> @@ -0,0 +1,43 @@
> +/* { dg-do run { target pthread_h } } */
> +/* { dg-shouldfail "asan" } */
> +/* { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1:halt_on_error=1" } */
> +/* { dg-options "-fsanitize=address,pointer-subtract" } */
> +/* { dg-additional-options "-pthread" { target pthread } } */
> +
> +#include <unistd.h>
> +#include <pthread.h>
> +
> +char *pointer;
> +pthread_barrier_t bar;
> +
> +void *
> +thread_main (void *n)
> +{
> +  char local;
> +  (void) n;
> +  pointer = &local;
> +  pthread_barrier_wait (&bar);
> +  pthread_barrier_wait (&bar);
> +
> +  return 0;
> +}
> +
> +int
> +main ()
> +{
> +  pthread_t thread;
> +  pthread_barrier_init (&bar, NULL, 2);
> +  pthread_create (&thread, NULL, thread_main, NULL);
> +  pthread_barrier_wait (&bar);
> +
> +  char local;
> +  char *parent_pointer = &local;
> +
> +  /* { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair" } */
> +  volatile __PTRDIFF_TYPE__ r = parent_pointer - pointer;
> +  pthread_barrier_wait (&bar);
> +  pthread_join (thread, NULL);
> +  pthread_barrier_destroy (&bar);
> +
> +  return 0;
> +}
> --- libsanitizer/asan/asan_descriptions.cc.jj	2017-11-21 23:18:20.440637693 +0100
> +++ libsanitizer/asan/asan_descriptions.cc	2017-12-04 22:38:28.155787511 +0100
> @@ -333,6 +333,26 @@ void GlobalAddressDescription::Print(con
>    }
>  }
>  
> +bool GlobalAddressDescription::PointsInsideTheSameVariable(
> +    const GlobalAddressDescription &other) const {
> +  if (size == 0 || other.size == 0) return false;
> +
> +  for (uptr i = 0; i < size; i++) {
> +    const __asan_global &a = globals[i];
> +    for (uptr j = 0; j < other.size; j++) {
> +      const __asan_global &b = other.globals[j];
> +      if (a.beg == b.beg &&
> +          a.beg <= addr &&
> +          b.beg <= other.addr &&
> +          (addr + access_size) < (a.beg + a.size) &&
> +          (other.addr + other.access_size) < (b.beg + b.size))
> +        return true;
> +    }
> +  }
> +
> +  return false;
> +}
> +
>  void StackAddressDescription::Print() const {
>    Decorator d;
>    char tname[128];
> --- libsanitizer/asan/asan_descriptions.h.jj	2017-11-21 23:18:20.435637754 +0100
> +++ libsanitizer/asan/asan_descriptions.h	2017-12-04 22:38:28.156787499 +0100
> @@ -143,6 +143,10 @@ struct GlobalAddressDescription {
>    u8 size;
>  
>    void Print(const char *bug_type = "") const;
> +
> +  // Returns true when this descriptions points inside the same global variable
> +  // as other. Descriptions can have different address within the variable
> +  bool PointsInsideTheSameVariable(const GlobalAddressDescription &other) const;
>  };
>  
>  bool GetGlobalAddressInformation(uptr addr, uptr access_size,
> --- libsanitizer/asan/asan_report.cc.jj	2017-11-21 23:18:20.435637754 +0100
> +++ libsanitizer/asan/asan_report.cc	2017-12-04 22:38:28.156787499 +0100
> @@ -295,17 +295,58 @@ static NOINLINE void ReportInvalidPointe
>    in_report.ReportError(error);
>  }
>  
> +static bool IsInvalidPointerPair(uptr a1, uptr a2) {
> +  if (a1 == a2)
> +    return false;
> +
> +  // 256B in shadow memory can be iterated quite fast
> +  static const uptr kMaxOffset = 2048;
> +
> +  uptr left = a1 < a2 ? a1 : a2;
> +  uptr right = a1 < a2 ? a2 : a1;
> +  uptr offset = right - left;
> +  if (offset <= kMaxOffset)
> +    return __asan_region_is_poisoned(left, offset);
> +
> +  AsanThread *t = GetCurrentThread();
> +
> +  // check whether left is a stack memory pointer
> +  if (uptr shadow_offset1 = t->GetStackVariableShadowStart(left)) {
> +    uptr shadow_offset2 = t->GetStackVariableShadowStart(right);
> +    return shadow_offset2 == 0 || shadow_offset1 != shadow_offset2;
> +  }
> +
> +  // check whether left is a heap memory address
> +  HeapAddressDescription hdesc1, hdesc2;
> +  if (GetHeapAddressInformation(left, 0, &hdesc1) &&
> +      hdesc1.chunk_access.access_type == kAccessTypeInside)
> +    return !GetHeapAddressInformation(right, 0, &hdesc2) ||
> +        hdesc2.chunk_access.access_type != kAccessTypeInside ||
> +        hdesc1.chunk_access.chunk_begin != hdesc2.chunk_access.chunk_begin;
> +
> +  // check whether left is an address of a global variable
> +  GlobalAddressDescription gdesc1, gdesc2;
> +  if (GetGlobalAddressInformation(left, 0, &gdesc1))
> +    return !GetGlobalAddressInformation(right - 1, 0, &gdesc2) ||
> +        !gdesc1.PointsInsideTheSameVariable(gdesc2);
> +
> +  if (t->GetStackVariableShadowStart(right) ||
> +      GetHeapAddressInformation(right, 0, &hdesc2) ||
> +      GetGlobalAddressInformation(right - 1, 0, &gdesc2))
> +    return true;
> +
> +  // At this point we know nothing about both a1 and a2 addresses.
> +  return false;
> +}
> +
>  static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
>    if (!flags()->detect_invalid_pointer_pairs) return;
>    uptr a1 = reinterpret_cast<uptr>(p1);
>    uptr a2 = reinterpret_cast<uptr>(p2);
> -  AsanChunkView chunk1 = FindHeapChunkByAddress(a1);
> -  AsanChunkView chunk2 = FindHeapChunkByAddress(a2);
> -  bool valid1 = chunk1.IsAllocated();
> -  bool valid2 = chunk2.IsAllocated();
> -  if (!valid1 || !valid2 || !chunk1.Eq(chunk2)) {
> +
> +  if (IsInvalidPointerPair(a1, a2)) {
>      GET_CALLER_PC_BP_SP;
> -    return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
> +    ReportInvalidPointerPair(pc, bp, sp, a1, a2);
>    }
>  }
>  // ----------------------- Mac-specific reports ----------------- {{{1
> --- libsanitizer/asan/asan_thread.cc.jj	2017-11-21 23:18:20.434637767 +0100
> +++ libsanitizer/asan/asan_thread.cc	2017-12-04 22:38:28.156787499 +0100
> @@ -315,7 +315,7 @@ bool AsanThread::GetStackFrameAccessByAd
>      access->frame_descr = (const char *)((uptr*)bottom)[1];
>      return true;
>    }
> -  uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1);  // align addr.
> +  uptr aligned_addr = RoundDownTo(addr, SANITIZER_WORDSIZE / 8);  // align addr.
>    uptr mem_ptr = RoundDownTo(aligned_addr, SHADOW_GRANULARITY);
>    u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
>    u8 *shadow_bottom = (u8*)MemToShadow(bottom);
> @@ -344,6 +344,29 @@ bool AsanThread::GetStackFrameAccessByAd
>    return true;
>  }
>  
> +uptr AsanThread::GetStackVariableShadowStart(uptr addr) {
> +  uptr bottom = 0;
> +  if (AddrIsInStack(addr)) {
> +    bottom = stack_bottom();
> +  } else if (has_fake_stack()) {
> +    bottom = fake_stack()->AddrIsInFakeStack(addr);
> +    CHECK(bottom);
> +  } else
> +    return 0;
> +
> +  uptr aligned_addr = RoundDownTo(addr, SANITIZER_WORDSIZE / 8);  // align addr.
> +  u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr);
> +  u8 *shadow_bottom = (u8*)MemToShadow(bottom);
> +
> +  while (shadow_ptr >= shadow_bottom &&
> +         (*shadow_ptr != kAsanStackLeftRedzoneMagic &&
> +          *shadow_ptr != kAsanStackMidRedzoneMagic &&
> +          *shadow_ptr != kAsanStackRightRedzoneMagic))
> +    shadow_ptr--;
> +
> +  return (uptr)shadow_ptr + 1;
> +}
> +
>  bool AsanThread::AddrIsInStack(uptr addr) {
>    const auto bounds = GetStackBounds();
>    return addr >= bounds.bottom && addr < bounds.top;
> --- libsanitizer/asan/asan_thread.h.jj	2017-11-21 23:18:20.435637754 +0100
> +++ libsanitizer/asan/asan_thread.h	2017-12-04 22:38:28.157787487 +0100
> @@ -88,6 +88,9 @@ class AsanThread {
>    };
>    bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
>  
> +  // Returns a pointer to the start of the stack variable's shadow memory.
> +  uptr GetStackVariableShadowStart(uptr addr);
> +
>    bool AddrIsInStack(uptr addr);
>  
>    void DeleteFakeStack(int tid) {
> 
> 
> 	Jakub
>
Martin Liška Dec. 21, 2017, 8:36 a.m. UTC | #22
On 12/05/2017 10:27 AM, Jakub Jelinek wrote:
> The most important change I've done in the testsuite was pointer-subtract-2.c
> used -fsanitize=address,pointer-subtract, but the function was actually
> doing pointer comparison.  Guess that needs to be propagated upstream at
> some point.  Another thing is that in pointer-compare-1.c where you test
> p - 1, p and p, p - 1 on the global variables, we need to ensure there is
> some other array before it, otherwise we run into the issue that there is no
> red zone before the first global (and when optimizing, global objects seems
> to be sorted by decreasing size).

Hi.

I've just done review request for that:
https://reviews.llvm.org/D41481

Apart from that I enhanced detect_invalid_pointer_pairs run-time option that
can control whether a pointer comparison (or subtraction) with nullptr is
reported or not:
https://reviews.llvm.org/D41479

Martin
diff mbox series

Patch

diff --git a/gcc/asan.c b/gcc/asan.c
index 2aa0a795af2..6feea017795 100644
--- a/gcc/asan.c
+++ b/gcc/asan.c
@@ -2370,6 +2370,104 @@  maybe_instrument_call (gimple_stmt_iterator *iter)
   return instrumented;
 }
 
+/* Return true if a given opcode CODE is potentially a non-valid comparison
+   of pointer types.  */
+
+static bool
+is_pointer_compare_opcode (tree_code code)
+{
+  return (code == LE_EXPR || code == LT_EXPR || code == GE_EXPR
+	  || code == GT_EXPR);
+}
+
+/* Instrument potential invalid operation executed on pointer types:
+   comparison different from != and == and subtraction of pointers.  */
+
+static void
+instrument_pointer_comparison (void)
+{
+  basic_block bb;
+  gimple_stmt_iterator i;
+
+  bool sanitize_comparison_p = sanitize_flags_p (SANITIZE_POINTER_COMPARE);
+  bool sanitize_subtraction_p = sanitize_flags_p (SANITIZE_POINTER_SUBTRACT);
+
+  FOR_EACH_BB_FN (bb, cfun)
+    {
+      for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
+	{
+	  gimple *s = gsi_stmt (i);
+
+	  tree ptr1 = NULL_TREE;
+	  tree ptr2 = NULL_TREE;
+	  enum built_in_function fn = BUILT_IN_NONE;
+
+	  if (sanitize_comparison_p)
+	    {
+	      if (is_gimple_assign (s)
+		  && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS
+		  && POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (s)))
+		  && POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs2 (s)))
+		  && is_pointer_compare_opcode (gimple_assign_rhs_code (s)))
+		{
+		  ptr1 = gimple_assign_rhs1 (s);
+		  ptr2 = gimple_assign_rhs2 (s);
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	      else if (gimple_code (s) == GIMPLE_COND
+		       && POINTER_TYPE_P (TREE_TYPE (gimple_cond_lhs (s)))
+		       && POINTER_TYPE_P (TREE_TYPE (gimple_cond_rhs (s)))
+		       && is_pointer_compare_opcode (gimple_cond_code (s)))
+		{
+		  ptr1 = gimple_cond_lhs (s);
+		  ptr2 = gimple_cond_rhs (s);
+		  fn = BUILT_IN_ASAN_POINTER_COMPARE;
+		}
+	    }
+
+	  if (sanitize_subtraction_p
+	      && is_gimple_assign (s)
+	      && gimple_assign_rhs_class (s) == GIMPLE_BINARY_RHS
+	      && gimple_assign_rhs_code (s) == MINUS_EXPR)
+	    {
+	      tree rhs1 = gimple_assign_rhs1 (s);
+	      tree rhs2 = gimple_assign_rhs2 (s);
+
+	      if (TREE_CODE (rhs1) == SSA_NAME
+		  || TREE_CODE (rhs2) == SSA_NAME)
+		{
+		  gassign *def1
+		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs1));
+		  gassign *def2
+		    = dyn_cast<gassign *>(SSA_NAME_DEF_STMT (rhs2));
+
+		  if (def1 && def2
+		      && gimple_assign_rhs_class (def1) == GIMPLE_UNARY_RHS
+		      && gimple_assign_rhs_class (def2) == GIMPLE_UNARY_RHS)
+		    {
+		      if (POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (def1)))
+			  && POINTER_TYPE_P
+			  (TREE_TYPE (gimple_assign_rhs1 (def2))))
+			{
+			  ptr1 = rhs1;
+			  ptr2 = rhs2;
+			  fn = BUILT_IN_ASAN_POINTER_SUBTRACT;
+			}
+		    }
+		}
+	    }
+
+	  if (ptr1 != NULL_TREE && ptr2 != NULL_TREE)
+	    {
+	      tree decl = builtin_decl_implicit (fn);
+	      gimple *g = gimple_build_call (decl, 2, ptr1, ptr2);
+	      gimple_set_location (g, gimple_location (s));
+	      gsi_insert_before (&i, g, GSI_SAME_STMT);
+	    }
+	}
+    }
+}
+
 /* Walk each instruction of all basic block and instrument those that
    represent memory references: loads, stores, or function calls.
    In a given basic block, this function avoids instrumenting memory
@@ -3432,6 +3530,9 @@  asan_instrument (void)
 {
   if (shadow_ptr_types[0] == NULL_TREE)
     asan_init_shadow_ptr_types ();
+
+  if (sanitize_flags_p (SANITIZE_POINTER_COMPARE | SANITIZE_POINTER_SUBTRACT))
+    instrument_pointer_comparison ();
   transform_statements ();
   last_alloca_addr = NULL_TREE;
   return 0;
@@ -3440,7 +3541,8 @@  asan_instrument (void)
 static bool
 gate_asan (void)
 {
-  return sanitize_flags_p (SANITIZE_ADDRESS);
+  return sanitize_flags_p (SANITIZE_ADDRESS | SANITIZE_POINTER_COMPARE
+			   | SANITIZE_POINTER_SUBTRACT);
 }
 
 namespace {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index f862b7f8c99..f481c29fbb9 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10935,6 +10935,26 @@  Enable AddressSanitizer for Linux kernel.
 See @uref{https://github.com/google/kasan/wiki} for more details.
 The option cannot be combined with @option{-fcheck-pointer-bounds}.
 
+@item -fsanitize=pointer-compare
+@opindex fsanitize=pointer-compare
+Instrument comparison operation (<, <=, >, >=, -) with pointer operands.
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.  The checking currently works only for pointers allocated
+on heap.
+
+@item -fsanitize=subtract
+@opindex fsanitize=pointer-compare
+Instrument subtraction with pointer operands.
+The option cannot be combined with @option{-fsanitize=thread}
+and/or @option{-fcheck-pointer-bounds}.
+Note: By default the check is disabled at run time.  To enable it,
+add @code{detect_invalid_pointer_pairs=1} to the environment variable
+@env{ASAN_OPTIONS}.  The checking currently works only for pointers allocated
+on heap.
+
 @item -fsanitize=thread
 @opindex fsanitize=thread
 Enable ThreadSanitizer, a fast data race detector.
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 1f439d35b07..74464651e00 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -246,6 +246,8 @@  enum sanitize_code {
   SANITIZE_VPTR = 1UL << 22,
   SANITIZE_BOUNDS_STRICT = 1UL << 23,
   SANITIZE_POINTER_OVERFLOW = 1UL << 24,
+  SANITIZE_POINTER_COMPARE = 1UL << 25,
+  SANITIZE_POINTER_SUBTRACT = 1UL << 26,
   SANITIZE_SHIFT = SANITIZE_SHIFT_BASE | SANITIZE_SHIFT_EXPONENT,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
diff --git a/gcc/gcc.c b/gcc/gcc.c
index cec3ed5be5f..20ac35ac03d 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -971,7 +971,9 @@  proper position among the other output files.  */
 /* Linker command line options for -fsanitize= early on the command line.  */
 #ifndef SANITIZER_EARLY_SPEC
 #define SANITIZER_EARLY_SPEC "\
-%{!nostdlib:%{!nodefaultlibs:%{%:sanitize(address):" LIBASAN_EARLY_SPEC "} \
+%{!nostdlib:%{!nodefaultlibs:%{%:sanitize(address)\
+    |%:sanitize(pointer-compare)\
+    |%:sanitize(pointer-subtract):" LIBASAN_EARLY_SPEC "} \
     %{%:sanitize(thread):" LIBTSAN_EARLY_SPEC "} \
     %{%:sanitize(leak):" LIBLSAN_EARLY_SPEC "}}}"
 #endif
@@ -981,6 +983,10 @@  proper position among the other output files.  */
 #define SANITIZER_SPEC "\
 %{!nostdlib:%{!nodefaultlibs:%{%:sanitize(address):" LIBASAN_SPEC "\
     %{static:%ecannot specify -static with -fsanitize=address}}\
+    %{%:sanitize(pointer-compare):" LIBASAN_SPEC "\
+    %{static:%ecannot specify -static with -fsanitize=pointer-compare}}\
+    %{%:sanitize(pointer-subtract):" LIBASAN_SPEC "\
+    %{static:%ecannot specify -static with -fsanitize=pointer-subtract}}\
     %{%:sanitize(thread):" LIBTSAN_SPEC "\
     %{static:%ecannot specify -static with -fsanitize=thread}}\
     %{%:sanitize(undefined):" LIBUBSAN_SPEC "}\
@@ -9421,6 +9427,10 @@  sanitize_spec_function (int argc, const char **argv)
     return (flag_sanitize & SANITIZE_USER_ADDRESS) ? "" : NULL;
   if (strcmp (argv[0], "kernel-address") == 0)
     return (flag_sanitize & SANITIZE_KERNEL_ADDRESS) ? "" : NULL;
+  if (strcmp (argv[0], "pointer-compare") == 0)
+    return (flag_sanitize & SANITIZE_POINTER_COMPARE) ? "" : NULL;
+  if (strcmp (argv[0], "pointer-subtract") == 0)
+    return (flag_sanitize & SANITIZE_POINTER_SUBTRACT) ? "" : NULL;
   if (strcmp (argv[0], "thread") == 0)
     return (flag_sanitize & SANITIZE_THREAD) ? "" : NULL;
   if (strcmp (argv[0], "undefined") == 0)
diff --git a/gcc/opts.c b/gcc/opts.c
index 5aa5d066dbe..6d45ddf574c 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -966,6 +966,14 @@  finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
 	      "%<-fsanitize=address%> and %<-fsanitize=kernel-address%> "
 	      "are incompatible with %<-fsanitize=thread%>");
 
+  if ((opts->x_flag_sanitize & SANITIZE_POINTER_COMPARE
+       || opts->x_flag_sanitize & SANITIZE_POINTER_SUBTRACT)
+      && (opts->x_flag_sanitize & SANITIZE_THREAD))
+    error_at (loc,
+	      "%<-fsanitize=pointer-compare%> and "
+	      "%<-fsanitize=pointer-subtract%> "
+	      "are incompatible with %<-fsanitize=thread%>");
+
   if ((opts->x_flag_sanitize & SANITIZE_LEAK)
       && (opts->x_flag_sanitize & SANITIZE_THREAD))
     error_at (loc,
@@ -1496,6 +1504,8 @@  const struct sanitizer_opts_s sanitizer_opts[] =
   SANITIZER_OPT (address, (SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS), true),
   SANITIZER_OPT (kernel-address, (SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
 		 true),
+  SANITIZER_OPT (pointer-compare, SANITIZE_POINTER_COMPARE, true),
+  SANITIZER_OPT (pointer-subtract, SANITIZE_POINTER_SUBTRACT, true),
   SANITIZER_OPT (thread, SANITIZE_THREAD, false),
   SANITIZER_OPT (leak, SANITIZE_LEAK, false),
   SANITIZER_OPT (shift, SANITIZE_SHIFT, true),
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 9d963f05c21..d06f68ba66e 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -175,6 +175,10 @@  DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCA_POISON, "__asan_alloca_poison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_ALLOCAS_UNPOISON, "__asan_allocas_unpoison",
 		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_COMPARE, "__sanitizer_ptr_cmp",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_POINTER_SUBTRACT, "__sanitizer_ptr_sub",
+		      BT_FN_VOID_PTR_PTRMODE, ATTR_NOTHROW_LEAF_LIST)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
new file mode 100644
index 00000000000..3d8ab19c9f9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-1.c
@@ -0,0 +1,31 @@ 
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int f(char *p, char *q)
+{
+  return p > q;
+}
+
+int f2(char *p)
+{
+  char *p2 = p + 20;
+  __builtin_free(p);
+  return p > p2;
+}
+
+int
+main ()
+{
+  char *p = (char *)__builtin_malloc(42);
+  char *q = (char *)__builtin_malloc(42);
+
+  int r = f(p, q) + f2(p);
+  __builtin_free (q);
+
+  return 1;
+}
+
+// { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*(\n|\r\n|\r)*" }
+// { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*(\n|\r\n|\r)" }
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
new file mode 100644
index 00000000000..fe6ff9bc7e1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-compare-2.c
@@ -0,0 +1,19 @@ 
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-compare -O0" }
+
+int f(char *p)
+{
+  char *p2 = p + 20;
+  return p > p2;
+}
+
+int
+main ()
+{
+  char *p = (char *)__builtin_malloc(42);
+
+  int r = f(p);
+  __builtin_free (p);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
new file mode 100644
index 00000000000..b69969c3091
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-1.c
@@ -0,0 +1,31 @@ 
+// { dg-do run }
+// { dg-shouldfail "asan" }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int f(char *p, char *q)
+{
+  return p - q;
+}
+
+int f2(char *p)
+{
+  char *p2 = p + 20;
+  __builtin_free(p);
+  return p2 - p;
+}
+
+int
+main ()
+{
+  char *p = (char *)__builtin_malloc(42);
+  char *q = (char *)__builtin_malloc(42);
+
+  int r = f(p, q) + f2(p);
+  __builtin_free (q);
+
+  return 1;
+}
+
+// { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*(\n|\r\n|\r)*" }
+// { dg-output "ERROR: AddressSanitizer: invalid-pointer-pair.*(\n|\r\n|\r)" }
diff --git a/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
new file mode 100644
index 00000000000..be4a3c5f2c2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/pointer-subtract-2.c
@@ -0,0 +1,19 @@ 
+// { dg-do run }
+// { dg-set-target-env-var ASAN_OPTIONS "detect_invalid_pointer_pairs=1 halt_on_error=0" }
+// { dg-options "-fsanitize=pointer-subtract -O0" }
+
+int f(char *p)
+{
+  char *p2 = p + 20;
+  return p - p2;
+}
+
+int
+main ()
+{
+  char *p = (char *)__builtin_malloc(42);
+
+  int r = f(p);
+  __builtin_free (p);
+  return 0;
+}
diff --git a/gcc/toplev.c b/gcc/toplev.c
index bee79d313b2..cd52f8776d1 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1302,7 +1302,9 @@  process_options (void)
 	  flag_check_pointer_bounds = 0;
 	}
 
-      if (flag_sanitize & SANITIZE_ADDRESS)
+      if (flag_sanitize & SANITIZE_ADDRESS
+	  || flag_sanitize & SANITIZE_POINTER_COMPARE
+	  || flag_sanitize & SANITIZE_POINTER_SUBTRACT)
 	{
 	  error_at (UNKNOWN_LOCATION,
 		    "%<-fcheck-pointer-bounds%> is not supported with "