asan: Disable sanitizer in IFUNC resolver

Message ID 20250912123123.2835471-1-hjl.tools@gmail.com
State New
Headers
Series asan: Disable sanitizer in IFUNC resolver |

Commit Message

H.J. Lu Sept. 12, 2025, 12:31 p.m. UTC
  Since IFUNC resolvers run early before the sanitizer run-time libraries
have a chance to set up the sanitizer infrastructure, disable sanitizer
in IFUNC resolver when the sanitizer infrastructure initialization is
needed.

gcc/

	PR sanitizer/110442
	* asan.h (sanitize_flags_p): Return false if the function is
	called by IFUNC resolver.
	(sanitize_coverage_p): Likewise.
	* cgraph.cc (called_by_ifunc_resolver_p): New.
	* internal-fn.cc (expand_TSAN_FUNC_EXIT): Return in function
	called by IFUNC resolver.
	* tree.h (called_by_ifunc_resolver_p): Likewise.

gcc/testsuite/

	PR sanitizer/110442
	* gcc.dg/asan/ifunc-1.c: New test.
	* gcc.dg/tsan/ifunc-1.c: Likewise.
	* gcc.dg/ubsan/ifunc-shift-1.c: Likewise.

Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
---
 gcc/asan.h                                 |  9 ++++-
 gcc/cgraph.cc                              |  9 +++++
 gcc/internal-fn.cc                         |  6 ++-
 gcc/testsuite/gcc.dg/asan/ifunc-1.c        | 37 ++++++++++++++++++
 gcc/testsuite/gcc.dg/tsan/ifunc-1.c        | 38 +++++++++++++++++++
 gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c | 44 ++++++++++++++++++++++
 gcc/tree.h                                 |  5 +++
 7 files changed, 144 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/asan/ifunc-1.c
 create mode 100644 gcc/testsuite/gcc.dg/tsan/ifunc-1.c
 create mode 100644 gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
  

Comments

H.J. Lu Nov. 28, 2025, 12:22 a.m. UTC | #1
On Fri, Sep 12, 2025 at 8:31 PM H.J. Lu <hjl.tools@gmail.com> wrote:
>
> Since IFUNC resolvers run early before the sanitizer run-time libraries
> have a chance to set up the sanitizer infrastructure, disable sanitizer
> in IFUNC resolver when the sanitizer infrastructure initialization is
> needed.
>
> gcc/
>
>         PR sanitizer/110442
>         * asan.h (sanitize_flags_p): Return false if the function is
>         called by IFUNC resolver.
>         (sanitize_coverage_p): Likewise.
>         * cgraph.cc (called_by_ifunc_resolver_p): New.
>         * internal-fn.cc (expand_TSAN_FUNC_EXIT): Return in function
>         called by IFUNC resolver.
>         * tree.h (called_by_ifunc_resolver_p): Likewise.
>
> gcc/testsuite/
>
>         PR sanitizer/110442
>         * gcc.dg/asan/ifunc-1.c: New test.
>         * gcc.dg/tsan/ifunc-1.c: Likewise.
>         * gcc.dg/ubsan/ifunc-shift-1.c: Likewise.
>
> Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
> ---
>  gcc/asan.h                                 |  9 ++++-
>  gcc/cgraph.cc                              |  9 +++++
>  gcc/internal-fn.cc                         |  6 ++-
>  gcc/testsuite/gcc.dg/asan/ifunc-1.c        | 37 ++++++++++++++++++
>  gcc/testsuite/gcc.dg/tsan/ifunc-1.c        | 38 +++++++++++++++++++
>  gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c | 44 ++++++++++++++++++++++
>  gcc/tree.h                                 |  5 +++
>  7 files changed, 144 insertions(+), 4 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/asan/ifunc-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/tsan/ifunc-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
>
> diff --git a/gcc/asan.h b/gcc/asan.h
> index a24562f67a2..999ed0dd818 100644
> --- a/gcc/asan.h
> +++ b/gcc/asan.h
> @@ -251,6 +251,10 @@ sanitize_flags_p (sanitize_code_type flag,
>
>    if (fn != NULL_TREE)
>      {
> +      if (TREE_CODE (fn) == FUNCTION_DECL
> +         && called_by_ifunc_resolver_p (fn))
> +       return false;
> +
>        tree value = lookup_attribute ("no_sanitize", DECL_ATTRIBUTES (fn));
>        if (value)
>         result_flags &= ~tree_to_uhwi (TREE_VALUE (value));
> @@ -266,8 +270,9 @@ sanitize_coverage_p (const_tree fn = current_function_decl)
>  {
>    return (flag_sanitize_coverage
>           && (fn == NULL_TREE
> -             || lookup_attribute ("no_sanitize_coverage",
> -                                  DECL_ATTRIBUTES (fn)) == NULL_TREE));
> +             || (!called_by_ifunc_resolver_p (fn)
> +                 && !lookup_attribute ("no_sanitize_coverage",
> +                                       DECL_ATTRIBUTES (fn)))));
>  }
>
>  #endif /* TREE_ASAN */
> diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
> index 32071a84bac..820c57695c2 100644
> --- a/gcc/cgraph.cc
> +++ b/gcc/cgraph.cc
> @@ -4494,6 +4494,15 @@ cgraph_node::former_thunk_p (void)
>    return true;
>  }
>
> +/* Return true if FN is called by an IFUNC resolver.  */
> +
> +bool
> +called_by_ifunc_resolver_p (const_tree fn)
> +{
> +  struct cgraph_node *c_node = cgraph_node::get (fn);
> +  return c_node && c_node->called_by_ifunc_resolver;
> +}
> +
>  /* A stashed copy of "symtab" for use by selftest::symbol_table_test.
>     This needs to be a global so that it can be a GC root, and thus
>     prevent the stashed copy from being garbage-collected if the GC runs
> diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc
> index bf2fac81807..f4cc0ce745d 100644
> --- a/gcc/internal-fn.cc
> +++ b/gcc/internal-fn.cc
> @@ -877,12 +877,14 @@ expand_ASAN_POISON_USE (internal_fn, gcall *)
>    gcc_unreachable ();
>  }
>
> -/* This should get expanded in the tsan pass.  */
> +/* This should get expanded in the tsan pass unless it is in a function
> +   called by IFUNC resolver.  */
>
>  static void
>  expand_TSAN_FUNC_EXIT (internal_fn, gcall *)
>  {
> -  gcc_unreachable ();
> +  if (!called_by_ifunc_resolver_p (current_function_decl))
> +    gcc_unreachable ();
>  }
>
>  /* This should get expanded in the lower pass.  */
> diff --git a/gcc/testsuite/gcc.dg/asan/ifunc-1.c b/gcc/testsuite/gcc.dg/asan/ifunc-1.c
> new file mode 100644
> index 00000000000..3e944c4fb67
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/asan/ifunc-1.c
> @@ -0,0 +1,37 @@
> +/* PR sanitizer/110442 */
> +/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
> +   due to ICE caused by compiler bug during IFUNC check.  */
> +/* { dg-do run { target { i?86-*-linux* x86_64-*-linux* } } } */
> +
> +#include <stdio.h>
> +
> +void
> +f1 (void)
> +{
> +  puts ("f1");
> +}
> +
> +void
> +f2 (void)
> +{
> +  puts ("f2");
> +}
> +
> +void *
> +resolve (void)
> +{
> +  __builtin_cpu_init ();
> +  if (__builtin_cpu_supports ("f16c"))
> +    return f1;
> +  else
> +    return f2;
> +}
> +
> +void f (void) __attribute__ ((ifunc ("resolve")));
> +
> +int
> +main (void)
> +{
> +  f ();
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/tsan/ifunc-1.c b/gcc/testsuite/gcc.dg/tsan/ifunc-1.c
> new file mode 100644
> index 00000000000..da477e33983
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tsan/ifunc-1.c
> @@ -0,0 +1,38 @@
> +/* PR sanitizer/110442 */
> +/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
> +   due to ICE caused by compiler bug during IFUNC check.  */
> +/* { dg-do run { target { x86_64-*-linux* } } } */
> +/* { dg-options "-fsanitize=thread" } */
> +
> +#include <stdio.h>
> +
> +void
> +f1 (void)
> +{
> +  puts ("f1");
> +}
> +
> +void
> +f2 (void)
> +{
> +  puts ("f2");
> +}
> +
> +void *
> +resolve (void)
> +{
> +  __builtin_cpu_init ();
> +  if (__builtin_cpu_supports ("f16c"))
> +    return f1;
> +  else
> +    return f2;
> +}
> +
> +void f (void) __attribute__ ((ifunc ("resolve")));
> +
> +int
> +main (void)
> +{
> +  f ();
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c b/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
> new file mode 100644
> index 00000000000..973097cf098
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
> @@ -0,0 +1,44 @@
> +/* PR sanitizer/110442 */
> +/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
> +   due to ICE caused by compiler bug during IFUNC check.  */
> +/* { dg-do run { target { i?86-*-linux* x86_64-*-linux* } } } */
> +/* { dg-options "-fsanitize=shift-exponent -Wno-shift-count-overflow" } */
> +
> +#include <stdio.h>
> +
> +int n = 30;
> +
> +void
> +f1 (void)
> +{
> +  puts ("f1");
> +}
> +
> +void
> +f2 (void)
> +{
> +  puts ("f2");
> +}
> +
> +void *
> +resolve (void)
> +{
> +  if ((n << 32) != 0)
> +    return f1;
> +  __builtin_cpu_init ();
> +  if (__builtin_cpu_supports ("f16c"))
> +    return f1;
> +  else
> +    return f2;
> +}
> +
> +void f (void) __attribute__ ((ifunc ("resolve")));
> +
> +int
> +main (void)
> +{
> +  f ();
> +  return 0;
> +}
> +
> +/* { dg-output "shift exponent 32 is too large for 32-bit type 'int'\[^\n\r]*(\n|\r\n|\r)" } */
> diff --git a/gcc/tree.h b/gcc/tree.h
> index ce8c778087f..706e704b2e2 100644
> --- a/gcc/tree.h
> +++ b/gcc/tree.h
> @@ -7113,4 +7113,9 @@ extern auto_vec<string_slice> get_clone_versions (const tree, int * = NULL);
>     directly.  */
>  extern auto_vec<string_slice> get_clone_attr_versions (const tree, int *);
>
> +/* In cgraph.cc */
> +
> +/* Return true if the function is called by an IFUNC resolver.  */
> +extern bool called_by_ifunc_resolver_p (const_tree);
> +
>  #endif  /* GCC_TREE_H  */
> --
> 2.51.0
>

PING^1.
  
Sam James April 23, 2026, 2:34 p.m. UTC | #2
"H.J. Lu" <hjl.tools@gmail.com> writes:

> On Fri, Sep 12, 2025 at 8:31 PM H.J. Lu <hjl.tools@gmail.com> wrote:
>>
>> Since IFUNC resolvers run early before the sanitizer run-time libraries
>> have a chance to set up the sanitizer infrastructure, disable sanitizer
>> in IFUNC resolver when the sanitizer infrastructure initialization is
>> needed.

Ping. We have been using this for a while.

>>
>> gcc/
>>
>>         PR sanitizer/110442
>>         * asan.h (sanitize_flags_p): Return false if the function is
>>         called by IFUNC resolver.
>>         (sanitize_coverage_p): Likewise.
>>         * cgraph.cc (called_by_ifunc_resolver_p): New.
>>         * internal-fn.cc (expand_TSAN_FUNC_EXIT): Return in function
>>         called by IFUNC resolver.
>>         * tree.h (called_by_ifunc_resolver_p): Likewise.
>>
>> gcc/testsuite/
>>
>>         PR sanitizer/110442
>>         * gcc.dg/asan/ifunc-1.c: New test.
>>         * gcc.dg/tsan/ifunc-1.c: Likewise.
>>         * gcc.dg/ubsan/ifunc-shift-1.c: Likewise.
>>
>> Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
>> ---
>>  gcc/asan.h                                 |  9 ++++-
>>  gcc/cgraph.cc                              |  9 +++++
>>  gcc/internal-fn.cc                         |  6 ++-
>>  gcc/testsuite/gcc.dg/asan/ifunc-1.c        | 37 ++++++++++++++++++
>>  gcc/testsuite/gcc.dg/tsan/ifunc-1.c        | 38 +++++++++++++++++++
>>  gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c | 44 ++++++++++++++++++++++
>>  gcc/tree.h                                 |  5 +++
>>  7 files changed, 144 insertions(+), 4 deletions(-)
>>  create mode 100644 gcc/testsuite/gcc.dg/asan/ifunc-1.c
>>  create mode 100644 gcc/testsuite/gcc.dg/tsan/ifunc-1.c
>>  create mode 100644 gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
>>
>> diff --git a/gcc/asan.h b/gcc/asan.h
>> index a24562f67a2..999ed0dd818 100644
>> --- a/gcc/asan.h
>> +++ b/gcc/asan.h
>> @@ -251,6 +251,10 @@ sanitize_flags_p (sanitize_code_type flag,
>>
>>    if (fn != NULL_TREE)
>>      {
>> +      if (TREE_CODE (fn) == FUNCTION_DECL
>> +         && called_by_ifunc_resolver_p (fn))
>> +       return false;
>> +
>>        tree value = lookup_attribute ("no_sanitize", DECL_ATTRIBUTES (fn));
>>        if (value)
>>         result_flags &= ~tree_to_uhwi (TREE_VALUE (value));
>> @@ -266,8 +270,9 @@ sanitize_coverage_p (const_tree fn = current_function_decl)
>>  {
>>    return (flag_sanitize_coverage
>>           && (fn == NULL_TREE
>> -             || lookup_attribute ("no_sanitize_coverage",
>> -                                  DECL_ATTRIBUTES (fn)) == NULL_TREE));
>> +             || (!called_by_ifunc_resolver_p (fn)
>> +                 && !lookup_attribute ("no_sanitize_coverage",
>> +                                       DECL_ATTRIBUTES (fn)))));
>>  }
>>
>>  #endif /* TREE_ASAN */
>> diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
>> index 32071a84bac..820c57695c2 100644
>> --- a/gcc/cgraph.cc
>> +++ b/gcc/cgraph.cc
>> @@ -4494,6 +4494,15 @@ cgraph_node::former_thunk_p (void)
>>    return true;
>>  }
>>
>> +/* Return true if FN is called by an IFUNC resolver.  */
>> +
>> +bool
>> +called_by_ifunc_resolver_p (const_tree fn)
>> +{
>> +  struct cgraph_node *c_node = cgraph_node::get (fn);
>> +  return c_node && c_node->called_by_ifunc_resolver;
>> +}
>> +
>>  /* A stashed copy of "symtab" for use by selftest::symbol_table_test.
>>     This needs to be a global so that it can be a GC root, and thus
>>     prevent the stashed copy from being garbage-collected if the GC runs
>> diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc
>> index bf2fac81807..f4cc0ce745d 100644
>> --- a/gcc/internal-fn.cc
>> +++ b/gcc/internal-fn.cc
>> @@ -877,12 +877,14 @@ expand_ASAN_POISON_USE (internal_fn, gcall *)
>>    gcc_unreachable ();
>>  }
>>
>> -/* This should get expanded in the tsan pass.  */
>> +/* This should get expanded in the tsan pass unless it is in a function
>> +   called by IFUNC resolver.  */
>>
>>  static void
>>  expand_TSAN_FUNC_EXIT (internal_fn, gcall *)
>>  {
>> -  gcc_unreachable ();
>> +  if (!called_by_ifunc_resolver_p (current_function_decl))
>> +    gcc_unreachable ();
>>  }
>>
>>  /* This should get expanded in the lower pass.  */
>> diff --git a/gcc/testsuite/gcc.dg/asan/ifunc-1.c b/gcc/testsuite/gcc.dg/asan/ifunc-1.c
>> new file mode 100644
>> index 00000000000..3e944c4fb67
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.dg/asan/ifunc-1.c
>> @@ -0,0 +1,37 @@
>> +/* PR sanitizer/110442 */
>> +/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
>> +   due to ICE caused by compiler bug during IFUNC check.  */
>> +/* { dg-do run { target { i?86-*-linux* x86_64-*-linux* } } } */
>> +
>> +#include <stdio.h>
>> +
>> +void
>> +f1 (void)
>> +{
>> +  puts ("f1");
>> +}
>> +
>> +void
>> +f2 (void)
>> +{
>> +  puts ("f2");
>> +}
>> +
>> +void *
>> +resolve (void)
>> +{
>> +  __builtin_cpu_init ();
>> +  if (__builtin_cpu_supports ("f16c"))
>> +    return f1;
>> +  else
>> +    return f2;
>> +}
>> +
>> +void f (void) __attribute__ ((ifunc ("resolve")));
>> +
>> +int
>> +main (void)
>> +{
>> +  f ();
>> +  return 0;
>> +}
>> diff --git a/gcc/testsuite/gcc.dg/tsan/ifunc-1.c b/gcc/testsuite/gcc.dg/tsan/ifunc-1.c
>> new file mode 100644
>> index 00000000000..da477e33983
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.dg/tsan/ifunc-1.c
>> @@ -0,0 +1,38 @@
>> +/* PR sanitizer/110442 */
>> +/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
>> +   due to ICE caused by compiler bug during IFUNC check.  */
>> +/* { dg-do run { target { x86_64-*-linux* } } } */
>> +/* { dg-options "-fsanitize=thread" } */
>> +
>> +#include <stdio.h>
>> +
>> +void
>> +f1 (void)
>> +{
>> +  puts ("f1");
>> +}
>> +
>> +void
>> +f2 (void)
>> +{
>> +  puts ("f2");
>> +}
>> +
>> +void *
>> +resolve (void)
>> +{
>> +  __builtin_cpu_init ();
>> +  if (__builtin_cpu_supports ("f16c"))
>> +    return f1;
>> +  else
>> +    return f2;
>> +}
>> +
>> +void f (void) __attribute__ ((ifunc ("resolve")));
>> +
>> +int
>> +main (void)
>> +{
>> +  f ();
>> +  return 0;
>> +}
>> diff --git a/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c b/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
>> new file mode 100644
>> index 00000000000..973097cf098
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
>> @@ -0,0 +1,44 @@
>> +/* PR sanitizer/110442 */
>> +/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
>> +   due to ICE caused by compiler bug during IFUNC check.  */
>> +/* { dg-do run { target { i?86-*-linux* x86_64-*-linux* } } } */
>> +/* { dg-options "-fsanitize=shift-exponent -Wno-shift-count-overflow" } */
>> +
>> +#include <stdio.h>
>> +
>> +int n = 30;
>> +
>> +void
>> +f1 (void)
>> +{
>> +  puts ("f1");
>> +}
>> +
>> +void
>> +f2 (void)
>> +{
>> +  puts ("f2");
>> +}
>> +
>> +void *
>> +resolve (void)
>> +{
>> +  if ((n << 32) != 0)
>> +    return f1;
>> +  __builtin_cpu_init ();
>> +  if (__builtin_cpu_supports ("f16c"))
>> +    return f1;
>> +  else
>> +    return f2;
>> +}
>> +
>> +void f (void) __attribute__ ((ifunc ("resolve")));
>> +
>> +int
>> +main (void)
>> +{
>> +  f ();
>> +  return 0;
>> +}
>> +
>> +/* { dg-output "shift exponent 32 is too large for 32-bit type 'int'\[^\n\r]*(\n|\r\n|\r)" } */
>> diff --git a/gcc/tree.h b/gcc/tree.h
>> index ce8c778087f..706e704b2e2 100644
>> --- a/gcc/tree.h
>> +++ b/gcc/tree.h
>> @@ -7113,4 +7113,9 @@ extern auto_vec<string_slice> get_clone_versions (const tree, int * = NULL);
>>     directly.  */
>>  extern auto_vec<string_slice> get_clone_attr_versions (const tree, int *);
>>
>> +/* In cgraph.cc */
>> +
>> +/* Return true if the function is called by an IFUNC resolver.  */
>> +extern bool called_by_ifunc_resolver_p (const_tree);
>> +
>>  #endif  /* GCC_TREE_H  */
>> --
>> 2.51.0
>>
>
> PING^1.
  
Jakub Jelinek April 23, 2026, 2:53 p.m. UTC | #3
On Thu, Apr 23, 2026 at 03:34:56PM +0100, Sam James wrote:
> "H.J. Lu" <hjl.tools@gmail.com> writes:
> 
> > On Fri, Sep 12, 2025 at 8:31 PM H.J. Lu <hjl.tools@gmail.com> wrote:
> >>
> >> Since IFUNC resolvers run early before the sanitizer run-time libraries
> >> have a chance to set up the sanitizer infrastructure, disable sanitizer
> >> in IFUNC resolver when the sanitizer infrastructure initialization is
> >> needed.
> 
> Ping. We have been using this for a while.

I thought I've commented on it but can find it only in my =sent and not
in gcc-patches archives.

Here is what I wrote back in September:

This looks like a really bad idea to me.                                                                                                                                              
It is fine to disable sanitization in the ifunc resolvers themselves by                                                                                                               
default (unless say user explicitly overrides it), but it is not fine                                                                                                                 
to silently disable sanitization of random other functions just because                                                                                                               
something called them somewhere from ifunc resolvers.                                                                                                                                 
Even worse, the property called by ifunc resolvers can change, during                                                                                                                 
parsing it might look as not called by them (so sanitization added during                                                                                                             
parsing (say ubsan shift/division etc.) might be enabled, but later on                                                                                                                
the same sanitization might go away because later in the TU it saw                                                                                                                    
a call from ifunc resolver), or the flag might get set later on.                                                                                                                      

	Jakub
  
Sam James April 23, 2026, 3:24 p.m. UTC | #4
Jakub Jelinek <jakub@redhat.com> writes:

> On Thu, Apr 23, 2026 at 03:34:56PM +0100, Sam James wrote:
>> "H.J. Lu" <hjl.tools@gmail.com> writes:
>> 
>> > On Fri, Sep 12, 2025 at 8:31 PM H.J. Lu <hjl.tools@gmail.com> wrote:
>> >>
>> >> Since IFUNC resolvers run early before the sanitizer run-time libraries
>> >> have a chance to set up the sanitizer infrastructure, disable sanitizer
>> >> in IFUNC resolver when the sanitizer infrastructure initialization is
>> >> needed.
>> 
>> Ping. We have been using this for a while.
>
> I thought I've commented on it but can find it only in my =sent and not
> in gcc-patches archives.

Ah, thanks.

>
> Here is what I wrote back in September:
>
> This looks like a really bad idea to me.                                                                                                                                              
> It is fine to disable sanitization in the ifunc resolvers themselves by                                                                                                               
> default (unless say user explicitly overrides it), but it is not fine                                                                                                                 
> to silently disable sanitization of random other functions just because                                                                                                               
> something called them somewhere from ifunc resolvers.

I think we informally say you should avoid calling functions inside
resolvers, but I don't see it documented anywhere explicitly. There is
old PR70082 for that though.

Florian had the same opinion on the bug but I've seen it repeated (and
said it myself) in places over the years.

> Even worse, the property called by ifunc resolvers can change, during                                                                                                                 
> parsing it might look as not called by them (so sanitization added during                                                                                                             
> parsing (say ubsan shift/division etc.) might be enabled, but later on                                                                                                                
> the same sanitization might go away because later in the TU it saw                                                                                                                    
> a call from ifunc resolver), or the flag might get set later on.                                                                                                                      
>

The alternatives seem to be: clones (!), or just disable it for the
resolver itself, and the rest is up to the user.

It looks like LLVM just did the resolver only.

> 	Jakub

sam
  
Sam James April 23, 2026, 3:33 p.m. UTC | #5
Sam James <sam@gentoo.org> writes:

> Jakub Jelinek <jakub@redhat.com> writes:
>
>> On Thu, Apr 23, 2026 at 03:34:56PM +0100, Sam James wrote:
>>> "H.J. Lu" <hjl.tools@gmail.com> writes:
>>> 
>>> > On Fri, Sep 12, 2025 at 8:31 PM H.J. Lu <hjl.tools@gmail.com> wrote:
>>> >>
>>> >> Since IFUNC resolvers run early before the sanitizer run-time libraries
>>> >> have a chance to set up the sanitizer infrastructure, disable sanitizer
>>> >> in IFUNC resolver when the sanitizer infrastructure initialization is
>>> >> needed.
>>> 
>>> Ping. We have been using this for a while.
>>
>> I thought I've commented on it but can find it only in my =sent and not
>> in gcc-patches archives.
>
> Ah, thanks.
>
>>
>> Here is what I wrote back in September:
>>
>> This looks like a really bad idea to me.                                                                                                                                              
>> It is fine to disable sanitization in the ifunc resolvers themselves by                                                                                                               
>> default (unless say user explicitly overrides it), but it is not fine                                                                                                                 
>> to silently disable sanitization of random other functions just because                                                                                                               
>> something called them somewhere from ifunc resolvers.
>
> I think we informally say you should avoid calling functions inside
> resolvers, but I don't see it documented anywhere explicitly. There is
> old PR70082 for that though.
>
> Florian had the same opinion on the bug but I've seen it repeated (and
> said it myself) in places over the years.
>
>> Even worse, the property called by ifunc resolvers can change, during                                                                                                                 
>> parsing it might look as not called by them (so sanitization added during                                                                                                             
>> parsing (say ubsan shift/division etc.) might be enabled, but later on                                                                                                                
>> the same sanitization might go away because later in the TU it saw                                                                                                                    
>> a call from ifunc resolver), or the flag might get set later on.                                                                                                                      
>>
>
> The alternatives seem to be: clones (!), or just disable it for the
> resolver itself, and the rest is up to the user.
>
> It looks like LLVM just did the resolver only.

Or we could make resolvers implicit gnu::flatten.

>
>> 	Jakub
>
> sam
  

Patch

diff --git a/gcc/asan.h b/gcc/asan.h
index a24562f67a2..999ed0dd818 100644
--- a/gcc/asan.h
+++ b/gcc/asan.h
@@ -251,6 +251,10 @@  sanitize_flags_p (sanitize_code_type flag,
 
   if (fn != NULL_TREE)
     {
+      if (TREE_CODE (fn) == FUNCTION_DECL
+	  && called_by_ifunc_resolver_p (fn))
+	return false;
+
       tree value = lookup_attribute ("no_sanitize", DECL_ATTRIBUTES (fn));
       if (value)
 	result_flags &= ~tree_to_uhwi (TREE_VALUE (value));
@@ -266,8 +270,9 @@  sanitize_coverage_p (const_tree fn = current_function_decl)
 {
   return (flag_sanitize_coverage
 	  && (fn == NULL_TREE
-	      || lookup_attribute ("no_sanitize_coverage",
-				   DECL_ATTRIBUTES (fn)) == NULL_TREE));
+	      || (!called_by_ifunc_resolver_p (fn)
+		  && !lookup_attribute ("no_sanitize_coverage",
+					DECL_ATTRIBUTES (fn)))));
 }
 
 #endif /* TREE_ASAN */
diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
index 32071a84bac..820c57695c2 100644
--- a/gcc/cgraph.cc
+++ b/gcc/cgraph.cc
@@ -4494,6 +4494,15 @@  cgraph_node::former_thunk_p (void)
   return true;
 }
 
+/* Return true if FN is called by an IFUNC resolver.  */
+
+bool
+called_by_ifunc_resolver_p (const_tree fn)
+{
+  struct cgraph_node *c_node = cgraph_node::get (fn);
+  return c_node && c_node->called_by_ifunc_resolver;
+}
+
 /* A stashed copy of "symtab" for use by selftest::symbol_table_test.
    This needs to be a global so that it can be a GC root, and thus
    prevent the stashed copy from being garbage-collected if the GC runs
diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc
index bf2fac81807..f4cc0ce745d 100644
--- a/gcc/internal-fn.cc
+++ b/gcc/internal-fn.cc
@@ -877,12 +877,14 @@  expand_ASAN_POISON_USE (internal_fn, gcall *)
   gcc_unreachable ();
 }
 
-/* This should get expanded in the tsan pass.  */
+/* This should get expanded in the tsan pass unless it is in a function
+   called by IFUNC resolver.  */
 
 static void
 expand_TSAN_FUNC_EXIT (internal_fn, gcall *)
 {
-  gcc_unreachable ();
+  if (!called_by_ifunc_resolver_p (current_function_decl))
+    gcc_unreachable ();
 }
 
 /* This should get expanded in the lower pass.  */
diff --git a/gcc/testsuite/gcc.dg/asan/ifunc-1.c b/gcc/testsuite/gcc.dg/asan/ifunc-1.c
new file mode 100644
index 00000000000..3e944c4fb67
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/ifunc-1.c
@@ -0,0 +1,37 @@ 
+/* PR sanitizer/110442 */
+/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
+   due to ICE caused by compiler bug during IFUNC check.  */
+/* { dg-do run { target { i?86-*-linux* x86_64-*-linux* } } } */
+
+#include <stdio.h>
+ 
+void
+f1 (void)
+{
+  puts ("f1");
+}
+ 
+void
+f2 (void)
+{
+  puts ("f2");
+}
+ 
+void *
+resolve (void)
+{
+  __builtin_cpu_init ();
+  if (__builtin_cpu_supports ("f16c"))
+    return f1;
+  else
+    return f2;
+}
+ 
+void f (void) __attribute__ ((ifunc ("resolve")));
+ 
+int
+main (void)
+{
+  f ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/tsan/ifunc-1.c b/gcc/testsuite/gcc.dg/tsan/ifunc-1.c
new file mode 100644
index 00000000000..da477e33983
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tsan/ifunc-1.c
@@ -0,0 +1,38 @@ 
+/* PR sanitizer/110442 */
+/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
+   due to ICE caused by compiler bug during IFUNC check.  */
+/* { dg-do run { target { x86_64-*-linux* } } } */
+/* { dg-options "-fsanitize=thread" } */
+
+#include <stdio.h>
+ 
+void
+f1 (void)
+{
+  puts ("f1");
+}
+ 
+void
+f2 (void)
+{
+  puts ("f2");
+}
+ 
+void *
+resolve (void)
+{
+  __builtin_cpu_init ();
+  if (__builtin_cpu_supports ("f16c"))
+    return f1;
+  else
+    return f2;
+}
+ 
+void f (void) __attribute__ ((ifunc ("resolve")));
+ 
+int
+main (void)
+{
+  f ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c b/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
new file mode 100644
index 00000000000..973097cf098
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/ubsan/ifunc-shift-1.c
@@ -0,0 +1,44 @@ 
+/* PR sanitizer/110442 */
+/* Don't use dg-require-ifunc to avoid marking this test as UNSUPPORTED
+   due to ICE caused by compiler bug during IFUNC check.  */
+/* { dg-do run { target { i?86-*-linux* x86_64-*-linux* } } } */
+/* { dg-options "-fsanitize=shift-exponent -Wno-shift-count-overflow" } */
+
+#include <stdio.h>
+ 
+int n = 30;
+
+void
+f1 (void)
+{
+  puts ("f1");
+}
+ 
+void
+f2 (void)
+{
+  puts ("f2");
+}
+ 
+void *
+resolve (void)
+{
+  if ((n << 32) != 0)
+    return f1;
+  __builtin_cpu_init ();
+  if (__builtin_cpu_supports ("f16c"))
+    return f1;
+  else
+    return f2;
+}
+ 
+void f (void) __attribute__ ((ifunc ("resolve")));
+ 
+int
+main (void)
+{
+  f ();
+  return 0;
+}
+
+/* { dg-output "shift exponent 32 is too large for 32-bit type 'int'\[^\n\r]*(\n|\r\n|\r)" } */
diff --git a/gcc/tree.h b/gcc/tree.h
index ce8c778087f..706e704b2e2 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -7113,4 +7113,9 @@  extern auto_vec<string_slice> get_clone_versions (const tree, int * = NULL);
    directly.  */
 extern auto_vec<string_slice> get_clone_attr_versions (const tree, int *);
 
+/* In cgraph.cc */
+
+/* Return true if the function is called by an IFUNC resolver.  */
+extern bool called_by_ifunc_resolver_p (const_tree);
+
 #endif  /* GCC_TREE_H  */