[v2,2/2] malloc: introduce ifuncs for malloc functions

Message ID 20260420141459.531612-3-yury.khrustalev@arm.com (mailing list archive)
State Under Review
Delegated to: DJ Delorie
Headers
Series malloc: introduce ifuncs for malloc functions |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed

Commit Message

Yury Khrustalev April 20, 2026, 2:14 p.m. UTC
  Introduce ifuncs and resolvers for functions pertinent to the
malloc interface.

In order to do this, we first rename all core implementations by
adding the '_core' suffix. These functions are supposed to be strictly
internal and must not be used beyond the malloc code. For example, the
__libc_malloc() function becomes __libc_malloc_core() and instead
__libc_malloc() becomes an ifunc with a corresponding resolver that
may return '__libc_malloc_core' as the implementation.

Each ifunc comes with a generic resolver that will be used unless a
target overrides the resolvers in its sysdeps directory. All resolvers
are supposed to be overridden together, and the logic of the generic
resolvers should be replicated by the target-specific ones.

This patch contains, as an example, aarch64-specific resolvers. At this
moment they are identical to the generic ones but in the future they can
be changed to support for features, e.g. to handle memory tagging.

Corresponding aliases are moved next to the ifuncs as well.

On targets that do not support ifuncs we alias externally visible
functions as well as the __libc_* functions to their respective
*_core symbols.
---
 malloc/Makefile                    |   6 ++
 malloc/malloc.c                    | 117 +++++++++++++++++------------
 malloc/tst-check-internal-calls.sh |  53 +++++++++++++
 sysdeps/aarch64/Makefile           |   3 +
 sysdeps/aarch64/malloc-ifuncs.c    |  85 +++++++++++++++++++++
 sysdeps/generic/Makefile           |   3 +
 sysdeps/generic/malloc-ifuncs.c    |  94 +++++++++++++++++++++++
 sysdeps/generic/malloc-ifuncs.h    |  61 +++++++++++++++
 8 files changed, 374 insertions(+), 48 deletions(-)
 create mode 100644 malloc/tst-check-internal-calls.sh
 create mode 100644 sysdeps/aarch64/malloc-ifuncs.c
 create mode 100644 sysdeps/generic/malloc-ifuncs.c
 create mode 100644 sysdeps/generic/malloc-ifuncs.h
  

Comments

Yury Khrustalev April 22, 2026, 1:30 p.m. UTC | #1
On Mon, Apr 20, 2026 at 03:14:59PM +0100, Yury Khrustalev wrote:
> Introduce ifuncs and resolvers for functions pertinent to the
> malloc interface.
>
> ...
>
> diff --git a/sysdeps/generic/malloc-ifuncs.c b/sysdeps/generic/malloc-ifuncs.c
> new file mode 100644
> index 0000000000..0c03c1466e
> --- /dev/null
> +++ b/sysdeps/generic/malloc-ifuncs.c
> @@ -0,0 +1,94 @@
>
> ...
>
> +#if IS_IN (libc)
> +
> +#include <malloc/malloc-internal.h>
> +#include <malloc-ifuncs.h>

I've noticed that this file fails to compile for Hurd targets because
malloc-internal.h includes calloc-clear-memory.h that has function
clear_memory() that uses memset(), and on Hurd it needs string.h.

The random inclusion of string.h in malloc.c (see above) seems to be
solving this issue. I think it's better to include string.h in the
calloc-clear-memory.h header that actually uses this function.

I will fix it in the next version of this patch.
  
Yury Khrustalev May 7, 2026, 12:51 p.m. UTC | #2
DJ,

On Mon, Apr 20, 2026 at 03:14:59PM +0100, Yury Khrustalev wrote:
> Introduce ifuncs and resolvers for functions pertinent to the
> malloc interface.
>
> ...
>

I've been working on fixing memory tagging in malloc that is based on
the ifuncs introduced in this patch. To help see my direction of work,
I've push it to the 'arm/malloc-mte-v1' branch [1].

In a nutshell, this is functionally correct MTE-based memory tagging in
malloc (any performance implications are out of scope at the moment).
We no longer need a separate configure option and memory tagging is
always present but not enabled unless both supported by hardware and
requested via a tunable.

This patch series also includes necessary refactoring of malloc code
that I am going to send for upstream review as soon as the ifunc patch
is accepted.

[1]: https://sourceware.org/git/?p=glibc.git;a=shortlog;h=refs/heads/arm/malloc-mte-v1

Cheers,
Yury
  
Adhemerval Zanella May 13, 2026, 1:32 p.m. UTC | #3
On 20/04/26 11:14, Yury Khrustalev wrote:
> Introduce ifuncs and resolvers for functions pertinent to the
> malloc interface.
> 
> In order to do this, we first rename all core implementations by
> adding the '_core' suffix. These functions are supposed to be strictly
> internal and must not be used beyond the malloc code. For example, the
> __libc_malloc() function becomes __libc_malloc_core() and instead
> __libc_malloc() becomes an ifunc with a corresponding resolver that
> may return '__libc_malloc_core' as the implementation.
> 
> Each ifunc comes with a generic resolver that will be used unless a
> target overrides the resolvers in its sysdeps directory. All resolvers
> are supposed to be overridden together, and the logic of the generic
> resolvers should be replicated by the target-specific ones.
> 
> This patch contains, as an example, aarch64-specific resolvers. At this
> moment they are identical to the generic ones but in the future they can
> be changed to support for features, e.g. to handle memory tagging.
> 
> Corresponding aliases are moved next to the ifuncs as well.
> 
> On targets that do not support ifuncs we alias externally visible
> functions as well as the __libc_* functions to their respective
> *_core symbols.

I think the potential issues I raised on the weekly calls are from current
IFUNC limitation (like [1] and [2]), so this patch should not add any
extra corner cases.

[1] https://sourceware.org/bugzilla/show_bug.cgi?id=20673
[2] https://sourceware.org/bugzilla/show_bug.cgi?id=23240> ---
>  malloc/Makefile                    |   6 ++
>  malloc/malloc.c                    | 117 +++++++++++++++++------------
>  malloc/tst-check-internal-calls.sh |  53 +++++++++++++
>  sysdeps/aarch64/Makefile           |   3 +
>  sysdeps/aarch64/malloc-ifuncs.c    |  85 +++++++++++++++++++++
>  sysdeps/generic/Makefile           |   3 +
>  sysdeps/generic/malloc-ifuncs.c    |  94 +++++++++++++++++++++++
>  sysdeps/generic/malloc-ifuncs.h    |  61 +++++++++++++++
>  8 files changed, 374 insertions(+), 48 deletions(-)
>  create mode 100644 malloc/tst-check-internal-calls.sh
>  create mode 100644 sysdeps/aarch64/malloc-ifuncs.c
>  create mode 100644 sysdeps/generic/malloc-ifuncs.c
>  create mode 100644 sysdeps/generic/malloc-ifuncs.h
> 
> diff --git a/malloc/Makefile b/malloc/Makefile
> index fef5021298..d8b2018a76 100644
> --- a/malloc/Makefile
> +++ b/malloc/Makefile
> @@ -411,6 +411,12 @@ endif
>  endif
>  endif
>  
> +tests-special += \
> +  $(objpfx)tst-check-internal-calls.out \
> +  # tests-special
> +$(objpfx)tst-check-internal-calls.out: tst-check-internal-calls.sh malloc.c
> +	$(SHELL) $^ > $@; $(evaluate-test)
> +
>  include ../Rules
>  
>  CFLAGS-mcheck-init.c += $(PIC-ccflag)
> diff --git a/malloc/malloc.c b/malloc/malloc.c
> index 57b58382b1..7da040b021 100644
> --- a/malloc/malloc.c
> +++ b/malloc/malloc.c
> @@ -566,8 +566,8 @@ tag_at (void *ptr)
>    differs across systems, but is in all cases less than the maximum
>    representable value of a size_t.
>  */
> -void *__libc_malloc (size_t);
> -libc_hidden_proto (__libc_malloc)
> +void *__libc_malloc_core (size_t);
> +libc_hidden_proto (__libc_malloc_core)
>  
>  static void *__libc_calloc2 (size_t);
>  static void *__libc_malloc2 (size_t);
> @@ -583,15 +583,16 @@ static void *__libc_malloc2 (size_t);
>    when possible, automatically trigger operations that give
>    back unused memory to the system, thus reducing program footprint.
>  */
> -void     __libc_free(void*);
> -libc_hidden_proto (__libc_free)
> +void __libc_free_core (void *);
> +libc_hidden_proto (__libc_free_core)
>  
>  /*
>    calloc(size_t n_elements, size_t element_size);
>    Returns a pointer to n_elements * element_size bytes, with all locations
>    set to zero.
>  */
> -void*  __libc_calloc(size_t, size_t);
> +void * __libc_calloc_core (size_t, size_t);
> +libc_hidden_proto (__libc_calloc_core)
>  
>  /*
>    realloc(void* p, size_t n)
> @@ -620,8 +621,8 @@ void*  __libc_calloc(size_t, size_t);
>    The old unix realloc convention of allowing the last-free'd chunk
>    to be used as an argument to realloc is not supported.
>  */
> -void*  __libc_realloc(void*, size_t);
> -libc_hidden_proto (__libc_realloc)
> +void *__libc_realloc_core (void *, size_t);
> +libc_hidden_proto (__libc_realloc_core)
>  
>  /*
>    memalign(size_t alignment, size_t n);
> @@ -635,16 +636,16 @@ libc_hidden_proto (__libc_realloc)
>  
>    Overreliance on memalign is a sure way to fragment space.
>  */
> -void*  __libc_memalign(size_t, size_t);
> -libc_hidden_proto (__libc_memalign)
> +void *__libc_memalign_core (size_t, size_t);
> +libc_hidden_proto (__libc_memalign_core)
>  
>  /*
>    valloc(size_t n);
>    Equivalent to memalign(pagesize, n), where pagesize is the page
>    size of the system. If the pagesize is unknown, 4096 is used.
>  */
> -void*  __libc_valloc(size_t);
> -
> +void *__libc_valloc_core (size_t);
> +libc_hidden_proto (__libc_valloc_core)
>  
>  
>  /*
> @@ -677,7 +678,8 @@ struct mallinfo __libc_mallinfo(void);
>    Equivalent to valloc(minimum-page-that-holds(n)), that is,
>    round up n to nearest pagesize.
>   */
> -void*  __libc_pvalloc(size_t);
> +void *__libc_pvalloc_core (size_t);
> +libc_hidden_proto (__libc_pvalloc_core)
>  
>  /*
>    malloc_trim(size_t pad);
> @@ -720,7 +722,8 @@ int      __malloc_trim(size_t);
>    assert(malloc_usable_size(p) >= 256);
>  
>  */
> -size_t   __malloc_usable_size(void*);
> +size_t __malloc_usable_size_core (void *);
> +libc_hidden_proto (__malloc_usable_size_core)
>  
>  /*
>    malloc_stats();
> @@ -2951,7 +2954,7 @@ tcache_key_initialize (void)
>  
>    /* We need tcache_key to be non-zero (otherwise tcache_double_free_verify's
>       clearing of e->key would go unnoticed and it would loop getting called
> -     through __libc_free), and we want tcache_key not to be a
> +     through __libc_free_core), and we want tcache_key not to be a
>       commonly-occurring value in memory, so ensure a minimum amount of one and
>       zero bits.  */
>    int minimum_bits = __WORDSIZE / 4;
> @@ -2983,7 +2986,7 @@ tcache_put_n (mchunkptr chunk, size_t tc_idx, tcache_entry **ep, bool mangled)
>  {
>    tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
>  
> -  /* Mark this chunk as "in the tcache" so the test in __libc_free will
> +  /* Mark this chunk as "in the tcache" so the test in __libc_free_core will
>       detect a double free.  */
>    e->key = tcache_key;
>  
> @@ -3142,7 +3145,7 @@ tcache_double_free_verify (tcache_entry *e)
>       or user data that happens to match the key.  Since we are not sure,
>       clear the key and retry freeing it.  */
>    e->key = 0;
> -  __libc_free (e);
> +  __libc_free_core (e);
>  }
>  
>  static void
> @@ -3258,7 +3261,7 @@ __libc_malloc2 (size_t bytes)
>  }
>  
>  void *
> -__libc_malloc (size_t bytes)
> +__libc_malloc_core (size_t bytes)
>  {
>  #if USE_TCACHE
>    size_t nb = checked_request2size (bytes);
> @@ -3284,17 +3287,17 @@ __libc_malloc (size_t bytes)
>  
>    return __libc_malloc2 (bytes);
>  }
> -libc_hidden_def (__libc_malloc)
> +libc_hidden_def (__libc_malloc_core)
>  
>  static void __attribute_noinline__
>  tcache_free_init (void *mem)
>  {
>    tcache_init (NULL);
> -  __libc_free (mem);
> +  __libc_free_core (mem);
>  }
>  
>  void
> -__libc_free (void *mem)
> +__libc_free_core (void *mem)
>  {
>    mchunkptr p;                          /* chunk corresponding to mem */
>  
> @@ -3352,10 +3355,10 @@ __libc_free (void *mem)
>  
>    _int_free_chunk (arena_for_chunk (p), p, size, 0);
>  }
> -libc_hidden_def (__libc_free)
> +libc_hidden_def (__libc_free_core)
>  
>  void *
> -__libc_realloc (void *oldmem, size_t bytes)
> +__libc_realloc_core (void *oldmem, size_t bytes)
>  {
>    mstate ar_ptr;
>    INTERNAL_SIZE_T nb;         /* padded request size */
> @@ -3364,12 +3367,12 @@ __libc_realloc (void *oldmem, size_t bytes)
>  
>    /* realloc of null is supposed to be same as malloc */
>    if (oldmem == NULL)
> -    return __libc_malloc (bytes);
> +    return __libc_malloc_core (bytes);
>  
>  #if REALLOC_ZERO_BYTES_FREES
>    if (bytes == 0)
>      {
> -      __libc_free (oldmem); return NULL;
> +      __libc_free_core (oldmem); return NULL;
>      }
>  #endif
>  
> @@ -3434,7 +3437,7 @@ __libc_realloc (void *oldmem, size_t bytes)
>  	return oldmem;
>  
>        /* Must alloc, copy, free. */
> -      newmem = __libc_malloc (bytes);
> +      newmem = __libc_malloc_core (bytes);
>        if (newmem == NULL)
>          return NULL;              /* propagate failure */
>  
> @@ -3466,7 +3469,7 @@ __libc_realloc (void *oldmem, size_t bytes)
>      {
>        /* Try harder to allocate memory in other arenas.  */
>        LIBC_PROBE (memory_realloc_retry, 2, bytes, oldmem);
> -      newp = __libc_malloc (bytes);
> +      newp = __libc_malloc_core (bytes);
>        if (newp != NULL)
>          {
>  	  size_t sz = memsize (oldp);
> @@ -3478,10 +3481,10 @@ __libc_realloc (void *oldmem, size_t bytes)
>  
>    return newp;
>  }
> -libc_hidden_def (__libc_realloc)
> +libc_hidden_def (__libc_realloc_core)
>  
>  void *
> -__libc_memalign (size_t alignment, size_t bytes)
> +__libc_memalign_core (size_t alignment, size_t bytes)
>  {
>    /* Round the alignment up to a power of 2.  Reject alignments that overflow
>       when rounded up.  Zero alignment is handled by _mid_memalign.  */
> @@ -3498,7 +3501,7 @@ __libc_memalign (size_t alignment, size_t bytes)
>  
>    return _mid_memalign (alignment, bytes);
>  }
> -libc_hidden_def (__libc_memalign)
> +libc_hidden_def (__libc_memalign_core)
>  
>  /* For ISO C17.  */
>  void *
> @@ -3551,7 +3554,7 @@ _mid_memalign (size_t alignment, size_t bytes)
>  
>    /* If we need less alignment than we give anyway, just relay to malloc.  */
>    if (alignment <= MALLOC_ALIGNMENT)
> -    return __libc_malloc (bytes);
> +    return __libc_malloc_core (bytes);
>  
>  #if USE_TCACHE
>    void *victim = tcache_get_align (checked_request2size (bytes), alignment);
> @@ -3586,13 +3589,14 @@ _mid_memalign (size_t alignment, size_t bytes)
>  }
>  
>  void *
> -__libc_valloc (size_t bytes)
> +__libc_valloc_core (size_t bytes)
>  {
>    return _mid_memalign (GLRO (dl_pagesize), bytes);
>  }
> +libc_hidden_def (__libc_valloc_core)
>  
>  void *
> -__libc_pvalloc (size_t bytes)
> +__libc_pvalloc_core (size_t bytes)
>  {
>    size_t pagesize = GLRO (dl_pagesize);
>    size_t rounded_bytes;
> @@ -3607,6 +3611,7 @@ __libc_pvalloc (size_t bytes)
>  
>    return _mid_memalign (pagesize, rounded_bytes & -pagesize);
>  }
> +libc_hidden_def (__libc_pvalloc_core)
>  
>  static void * __attribute_noinline__
>  __libc_calloc2 (size_t sz)
> @@ -3703,7 +3708,7 @@ __libc_calloc2 (size_t sz)
>  }
>  
>  void *
> -__libc_calloc (size_t n, size_t elem_size)
> +__libc_calloc_core (size_t n, size_t elem_size)
>  {
>    size_t bytes;
>  
> @@ -3747,6 +3752,7 @@ __libc_calloc (size_t n, size_t elem_size)
>  #endif
>    return __libc_calloc2 (bytes);
>  }
> +libc_hidden_def (__libc_calloc_core)
>  #endif /* IS_IN (libc) */
>  
>  /*
> @@ -4754,12 +4760,13 @@ musable (void *mem)
>  
>  #if IS_IN (libc)
>  size_t
> -__malloc_usable_size (void *m)
> +__malloc_usable_size_core (void *m)
>  {
>    if (m == NULL)
>      return 0;
>    return musable (m);
>  }
> +libc_hidden_def (__malloc_usable_size_core)
>  #endif
>  
>  /*
> @@ -5458,26 +5465,40 @@ __malloc_info (int options, FILE *fp)
>  }
>  #if IS_IN (libc)
>  weak_alias (__malloc_info, malloc_info)
> -
> -weak_alias (__libc_calloc, calloc)
> -strong_alias (__libc_free, free)
> -strong_alias (__libc_malloc, malloc)
> -weak_alias (__libc_memalign, memalign)
> -strong_alias (__libc_realloc, realloc)
> -weak_alias (__libc_valloc, valloc)
> -weak_alias (__libc_pvalloc, pvalloc)
>  weak_alias (__libc_mallinfo, mallinfo)
>  weak_alias (__libc_mallinfo2, mallinfo2)
>  weak_alias (__libc_mallopt, mallopt)
> -
>  weak_alias (__malloc_stats, malloc_stats)
> -weak_alias (__malloc_usable_size, malloc_usable_size)
>  weak_alias (__malloc_trim, malloc_trim)
> -#endif
>  
> -#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
> -compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
> -#endif
> +/* On targets that do not support ifuncs we alias externally visible
> +   functions as well as the __libc_* functions to their respective
> +   *_core symbols.
> +
> +   For ifunc prototypes and resolvers see sysdeps/generic/malloc-ifuncs.h.
> + */
> +# if !HAVE_IFUNC
> +strong_alias (__libc_malloc_core, malloc)
> +strong_alias (__libc_malloc_core, __libc_malloc)
> +weak_alias (__libc_calloc_core, calloc)
> +strong_alias (__libc_calloc_core, __libc_calloc)
> +weak_alias (__libc_memalign_core, memalign)
> +strong_alias (__libc_memalign_core, __libc_memalign)
> +weak_alias (__libc_valloc_core, valloc)
> +strong_alias (__libc_valloc_core, __libc_valloc)
> +weak_alias (__libc_pvalloc_core, pvalloc)
> +strong_alias (__libc_pvalloc_core, __libc_pvalloc)
> +strong_alias (__libc_realloc_core, realloc)
> +strong_alias (__libc_realloc_core, __libc_realloc)
> +#  if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
> +compat_symbol (libc, __libc_free_core, cfree, GLIBC_2_0);
> +#  endif
> +strong_alias (__libc_free_core, free)
> +strong_alias (__libc_free_core, __libc_free)
> +weak_alias (__malloc_usable_size_core, malloc_usable_size)
> +# endif /* !HAVE_IFUNC */
> +
> +#endif /* IS_IN (libc) */
>  
>  /* ------------------------------------------------------------
>     History:
> diff --git a/malloc/tst-check-internal-calls.sh b/malloc/tst-check-internal-calls.sh
> new file mode 100644
> index 0000000000..3558c15491
> --- /dev/null
> +++ b/malloc/tst-check-internal-calls.sh
> @@ -0,0 +1,53 @@
> +#!/bin/sh
> +# This script checks that malloc.c does not use any external API
> +# functions.
> +# It is important that whenever an internal function needs to used e.g.
> +# __libc_foo() it actually uses __libc_foo_core() symbol. If it uses a
> +# non-_core symbol, the returned result may not be suitable for the
> +# subsequent use of it internally.
> +# The non-_core malloc functions return and accept user-pointers which
> +# are different from the internal-pointers that are used by the _core
> +# functions.
> +
> +# Copyright (C) 2026 Free Software Foundation, Inc.
> +# This file is part of the GNU C Library.
> +
> +# The GNU C Library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +
> +# The GNU C Library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with the GNU C Library; if not, see
> +# <https://www.gnu.org/licenses/>.
> +
> +# This script accepts list of source files that need to be grep-ed for
> +# certain symbols and the result should be no matches except for the
> +# alias declarations
> +
> +status=0
> +
> +for src; do
> +  echo "checking $src..."
> +  for fun in \
> +    __libc_malloc \
> +    __libc_calloc \
> +    __libc_memalign \
> +    __libc_valloc \
> +    __libc_pvalloc \
> +    __libc_realloc \
> +    __libc_free; do
> +    grep -nw "$fun" $src | egrep -vw "^[0-9]+:(strong_alias|weak_alias)" && {
> +      good=$fun"_core"
> +      echo "error: code in $src should not use '$fun' (use '$good' instead)"
> +      status=1
> +    }
> +  done
> +done
> +
> +exit $status

Should we extend the tests for ree_sized/free_aligned_sized? They currently calls
free, but a MTE allocator might use a different strategy.

> diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
> index 1f7b01447b..517f44373a 100644
> --- a/sysdeps/aarch64/Makefile
> +++ b/sysdeps/aarch64/Makefile
> @@ -102,6 +102,9 @@ $(objpfx)tst-sme-clone3: $(objpfx)clone3.o $(objpfx)__arm_za_disable.o
>  endif
>  
>  ifeq ($(subdir),malloc)
> +sysdep_routines += \
> +  malloc-ifuncs \
> +  # sysdep_routines
>  sysdep_malloc_debug_routines = \
>    __mtag_tag_region \
>    __mtag_tag_zero_region \
> diff --git a/sysdeps/aarch64/malloc-ifuncs.c b/sysdeps/aarch64/malloc-ifuncs.c
> new file mode 100644
> index 0000000000..9986bab851
> --- /dev/null
> +++ b/sysdeps/aarch64/malloc-ifuncs.c
> @@ -0,0 +1,85 @@
> +/* Code for ifunc resolvers for malloc: aarch64 version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#if IS_IN (libc)
> +
> +#include <malloc/malloc-internal.h>
> +#include <malloc-ifuncs.h>
> +
> +/* AArch64-specific resolvers for malloc ifuncs.  */
> +
> +IFUNC_PROTO (__libc_malloc);
> +IFUNC_RESOLVER (__libc_malloc, uint64_t arg0, uint64_t arg1[])

Should the last argument be 'const'?

> +{
> +  return __libc_malloc_core;
> +}
> +strong_alias (__libc_malloc, malloc)
> +
> +IFUNC_PROTO (__libc_calloc);
> +IFUNC_RESOLVER (__libc_calloc, uint64_t arg0, uint64_t arg1[])
> +{
> +  return __libc_calloc_core;
> +}
> +weak_alias (__libc_calloc, calloc)
> +
> +IFUNC_PROTO (__libc_memalign);
> +IFUNC_RESOLVER (__libc_memalign, uint64_t arg0, uint64_t arg1[])
> +{
> +  return __libc_memalign_core;
> +}
> +weak_alias (__libc_memalign, memalign)
> +
> +IFUNC_PROTO (__libc_valloc);
> +IFUNC_RESOLVER (__libc_valloc, uint64_t arg0, uint64_t arg1[])
> +{
> +  return __libc_valloc_core;
> +}
> +weak_alias (__libc_valloc, valloc)
> +
> +IFUNC_PROTO (__libc_pvalloc);
> +IFUNC_RESOLVER (__libc_pvalloc, uint64_t arg0, uint64_t arg1[])
> +{
> +  return __libc_pvalloc_core;
> +}
> +weak_alias (__libc_pvalloc, pvalloc)
> +
> +IFUNC_PROTO (__libc_realloc);
> +IFUNC_RESOLVER (__libc_realloc, uint64_t arg0, uint64_t arg1[])
> +{
> +  return __libc_realloc_core;
> +}
> +strong_alias (__libc_realloc, realloc)
> +
> +IFUNC_PROTO (__libc_free);
> +IFUNC_RESOLVER (__libc_free, uint64_t arg0, uint64_t arg1[])
> +{
> +  return __libc_free_core;
> +}
> +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
> +compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
> +# endif
> +strong_alias (__libc_free, free)
> +
> +IFUNC_PROTO (__malloc_usable_size);
> +IFUNC_RESOLVER (__malloc_usable_size, uint64_t arg0, uint64_t arg1[])
> +{
> +  return __malloc_usable_size_core;
> +}
> +weak_alias (__malloc_usable_size, malloc_usable_size)
> +
> +#endif /* IS_IN (libc) */
> diff --git a/sysdeps/generic/Makefile b/sysdeps/generic/Makefile
> index 0b586efe63..6816cf907d 100644
> --- a/sysdeps/generic/Makefile
> +++ b/sysdeps/generic/Makefile
> @@ -43,6 +43,9 @@ endif
>  endif
>  
>  ifeq ($(subdir),malloc)
> +sysdep_routines += \
> +  malloc-ifuncs \
> +  # sysdep_routines
>  sysdep_malloc_debug_routines += \
>    hugepages \
>    # sysdep_malloc_debug_routines
> diff --git a/sysdeps/generic/malloc-ifuncs.c b/sysdeps/generic/malloc-ifuncs.c
> new file mode 100644
> index 0000000000..0c03c1466e
> --- /dev/null
> +++ b/sysdeps/generic/malloc-ifuncs.c
> @@ -0,0 +1,94 @@
> +/* Code for ifunc resolvers for malloc: generic version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#if IS_IN (libc)
> +
> +#include <malloc/malloc-internal.h>
> +#include <malloc-ifuncs.h>
> +
> +# if HAVE_IFUNC
> +
> +/* These resolvers are used by default unless overridden by a target.
> +   The target-specific resolvers must respect this logic of the default
> +   resolvers, replicating this logic where appropriate.
> +
> +   Any aliases for malloc API functions must be defined here as well
> +   and re-defined along with the target-specific resolvers.  */
> +
> +IFUNC_PROTO (__libc_malloc);
> +IFUNC_RESOLVER (__libc_malloc, void)
> +{
> +  return __libc_malloc_core;
> +}
> +strong_alias (__libc_malloc, malloc)
> +
> +IFUNC_PROTO (__libc_calloc);
> +IFUNC_RESOLVER (__libc_calloc, void)
> +{
> +  return __libc_calloc_core;
> +}
> +weak_alias (__libc_calloc, calloc)
> +
> +IFUNC_PROTO (__libc_memalign);
> +IFUNC_RESOLVER (__libc_memalign, void)
> +{
> +  return __libc_memalign_core;
> +}
> +weak_alias (__libc_memalign, memalign)
> +
> +IFUNC_PROTO (__libc_valloc);
> +IFUNC_RESOLVER (__libc_valloc, void)
> +{
> +  return __libc_valloc_core;
> +}
> +weak_alias (__libc_valloc, valloc)
> +
> +IFUNC_PROTO (__libc_pvalloc);
> +IFUNC_RESOLVER (__libc_pvalloc, void)
> +{
> +  return __libc_pvalloc_core;
> +}
> +weak_alias (__libc_pvalloc, pvalloc)
> +
> +IFUNC_PROTO (__libc_realloc);
> +IFUNC_RESOLVER (__libc_realloc, void)
> +{
> +  return __libc_realloc_core;
> +}
> +strong_alias (__libc_realloc, realloc)
> +
> +IFUNC_PROTO (__libc_free);
> +IFUNC_RESOLVER (__libc_free, void)
> +{
> +  return __libc_free_core;
> +}
> +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
> +compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
> +# endif
> +strong_alias (__libc_free, free)
> +
> +IFUNC_PROTO (__malloc_usable_size);
> +IFUNC_RESOLVER (__malloc_usable_size, void)
> +{
> +  return __malloc_usable_size_core;
> +}
> +weak_alias (__malloc_usable_size, malloc_usable_size)
> +
> +# endif /* HAVE_IFUNC */
> +
> +#endif /* IS_IN (libc) */
> diff --git a/sysdeps/generic/malloc-ifuncs.h b/sysdeps/generic/malloc-ifuncs.h
> new file mode 100644
> index 0000000000..1d3ddb0202
> --- /dev/null
> +++ b/sysdeps/generic/malloc-ifuncs.h
> @@ -0,0 +1,61 @@
> +/* Definitions for ifunc resolvers for malloc: generic version.
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#ifndef _GENERIC_MALLOC_IFUNCS_H
> +#define _GENERIC_MALLOC_IFUNCS_H
> +
> +#if HAVE_IFUNC
> +
> +#include <stddef.h>
> +#include <sys/cdefs.h>
> +#include <shlib-compat.h>
> +
> +/* Core implementations of malloc functions.  An ifunc resolver must
> +   use this implementations as a fallback option.  Other implementations
> +   may internally call these core function.  */
> +void *__libc_malloc_core (size_t);
> +libc_hidden_proto (__libc_malloc_core)
> +void *__libc_calloc_core (size_t, size_t);
> +libc_hidden_proto (__libc_calloc_core)
> +void *__libc_memalign_core (size_t, size_t);
> +libc_hidden_proto (__libc_memalign_core)
> +void *__libc_valloc_core (size_t);
> +libc_hidden_proto (__libc_valloc_core)
> +void *__libc_pvalloc_core (size_t);
> +libc_hidden_proto (__libc_pvalloc_core)
> +void *__libc_realloc_core (void *, size_t);
> +libc_hidden_proto (__libc_realloc_core)
> +void __libc_free_core (void *);
> +libc_hidden_proto (__libc_free_core)
> +size_t __malloc_usable_size_core (void *);
> +libc_hidden_proto (__malloc_usable_size_core)
> +
> +/* Macros for defining ifunc resolvers for malloc functions.  */
> +#define IFUNC_RESOLVER_NAME(fn) fn ## _resolver
> +#define STR(x) #x
> +#define XSTR(x) STR(x)
> +#define IFUNC_PROTO(fn) \
> +  __typeof (fn ## _core) fn \
> +  __attribute__ ((ifunc (XSTR(IFUNC_RESOLVER_NAME(fn)))))
> +#define IFUNC_RESOLVER(fn, ...) \
> +  static __attribute_used__ \
> +  __typeof (fn ## _core) *IFUNC_RESOLVER_NAME(fn) (__VA_ARGS__)

This duplicatd the already provided ifunc support from libc-symbols.h (libc_ifunc,
__ifunc_args, __ifunc_hidden), which handles inhibit_stack_protector (which is
missing here) and the fallback for !HAVE_GCC_IFUNC.  I think we should coalesce
the ifunc macros on only one place to avoid definitions drifiting and missing
handling (like the stack protector here). 

> +
> +#endif /* HAVE_IFUNC */
> +
> +#endif /* _GENERIC_MALLOC_IFUNCS_H */
  

Patch

diff --git a/malloc/Makefile b/malloc/Makefile
index fef5021298..d8b2018a76 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -411,6 +411,12 @@  endif
 endif
 endif
 
+tests-special += \
+  $(objpfx)tst-check-internal-calls.out \
+  # tests-special
+$(objpfx)tst-check-internal-calls.out: tst-check-internal-calls.sh malloc.c
+	$(SHELL) $^ > $@; $(evaluate-test)
+
 include ../Rules
 
 CFLAGS-mcheck-init.c += $(PIC-ccflag)
diff --git a/malloc/malloc.c b/malloc/malloc.c
index 57b58382b1..7da040b021 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -566,8 +566,8 @@  tag_at (void *ptr)
   differs across systems, but is in all cases less than the maximum
   representable value of a size_t.
 */
-void *__libc_malloc (size_t);
-libc_hidden_proto (__libc_malloc)
+void *__libc_malloc_core (size_t);
+libc_hidden_proto (__libc_malloc_core)
 
 static void *__libc_calloc2 (size_t);
 static void *__libc_malloc2 (size_t);
@@ -583,15 +583,16 @@  static void *__libc_malloc2 (size_t);
   when possible, automatically trigger operations that give
   back unused memory to the system, thus reducing program footprint.
 */
-void     __libc_free(void*);
-libc_hidden_proto (__libc_free)
+void __libc_free_core (void *);
+libc_hidden_proto (__libc_free_core)
 
 /*
   calloc(size_t n_elements, size_t element_size);
   Returns a pointer to n_elements * element_size bytes, with all locations
   set to zero.
 */
-void*  __libc_calloc(size_t, size_t);
+void * __libc_calloc_core (size_t, size_t);
+libc_hidden_proto (__libc_calloc_core)
 
 /*
   realloc(void* p, size_t n)
@@ -620,8 +621,8 @@  void*  __libc_calloc(size_t, size_t);
   The old unix realloc convention of allowing the last-free'd chunk
   to be used as an argument to realloc is not supported.
 */
-void*  __libc_realloc(void*, size_t);
-libc_hidden_proto (__libc_realloc)
+void *__libc_realloc_core (void *, size_t);
+libc_hidden_proto (__libc_realloc_core)
 
 /*
   memalign(size_t alignment, size_t n);
@@ -635,16 +636,16 @@  libc_hidden_proto (__libc_realloc)
 
   Overreliance on memalign is a sure way to fragment space.
 */
-void*  __libc_memalign(size_t, size_t);
-libc_hidden_proto (__libc_memalign)
+void *__libc_memalign_core (size_t, size_t);
+libc_hidden_proto (__libc_memalign_core)
 
 /*
   valloc(size_t n);
   Equivalent to memalign(pagesize, n), where pagesize is the page
   size of the system. If the pagesize is unknown, 4096 is used.
 */
-void*  __libc_valloc(size_t);
-
+void *__libc_valloc_core (size_t);
+libc_hidden_proto (__libc_valloc_core)
 
 
 /*
@@ -677,7 +678,8 @@  struct mallinfo __libc_mallinfo(void);
   Equivalent to valloc(minimum-page-that-holds(n)), that is,
   round up n to nearest pagesize.
  */
-void*  __libc_pvalloc(size_t);
+void *__libc_pvalloc_core (size_t);
+libc_hidden_proto (__libc_pvalloc_core)
 
 /*
   malloc_trim(size_t pad);
@@ -720,7 +722,8 @@  int      __malloc_trim(size_t);
   assert(malloc_usable_size(p) >= 256);
 
 */
-size_t   __malloc_usable_size(void*);
+size_t __malloc_usable_size_core (void *);
+libc_hidden_proto (__malloc_usable_size_core)
 
 /*
   malloc_stats();
@@ -2951,7 +2954,7 @@  tcache_key_initialize (void)
 
   /* We need tcache_key to be non-zero (otherwise tcache_double_free_verify's
      clearing of e->key would go unnoticed and it would loop getting called
-     through __libc_free), and we want tcache_key not to be a
+     through __libc_free_core), and we want tcache_key not to be a
      commonly-occurring value in memory, so ensure a minimum amount of one and
      zero bits.  */
   int minimum_bits = __WORDSIZE / 4;
@@ -2983,7 +2986,7 @@  tcache_put_n (mchunkptr chunk, size_t tc_idx, tcache_entry **ep, bool mangled)
 {
   tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
 
-  /* Mark this chunk as "in the tcache" so the test in __libc_free will
+  /* Mark this chunk as "in the tcache" so the test in __libc_free_core will
      detect a double free.  */
   e->key = tcache_key;
 
@@ -3142,7 +3145,7 @@  tcache_double_free_verify (tcache_entry *e)
      or user data that happens to match the key.  Since we are not sure,
      clear the key and retry freeing it.  */
   e->key = 0;
-  __libc_free (e);
+  __libc_free_core (e);
 }
 
 static void
@@ -3258,7 +3261,7 @@  __libc_malloc2 (size_t bytes)
 }
 
 void *
-__libc_malloc (size_t bytes)
+__libc_malloc_core (size_t bytes)
 {
 #if USE_TCACHE
   size_t nb = checked_request2size (bytes);
@@ -3284,17 +3287,17 @@  __libc_malloc (size_t bytes)
 
   return __libc_malloc2 (bytes);
 }
-libc_hidden_def (__libc_malloc)
+libc_hidden_def (__libc_malloc_core)
 
 static void __attribute_noinline__
 tcache_free_init (void *mem)
 {
   tcache_init (NULL);
-  __libc_free (mem);
+  __libc_free_core (mem);
 }
 
 void
-__libc_free (void *mem)
+__libc_free_core (void *mem)
 {
   mchunkptr p;                          /* chunk corresponding to mem */
 
@@ -3352,10 +3355,10 @@  __libc_free (void *mem)
 
   _int_free_chunk (arena_for_chunk (p), p, size, 0);
 }
-libc_hidden_def (__libc_free)
+libc_hidden_def (__libc_free_core)
 
 void *
-__libc_realloc (void *oldmem, size_t bytes)
+__libc_realloc_core (void *oldmem, size_t bytes)
 {
   mstate ar_ptr;
   INTERNAL_SIZE_T nb;         /* padded request size */
@@ -3364,12 +3367,12 @@  __libc_realloc (void *oldmem, size_t bytes)
 
   /* realloc of null is supposed to be same as malloc */
   if (oldmem == NULL)
-    return __libc_malloc (bytes);
+    return __libc_malloc_core (bytes);
 
 #if REALLOC_ZERO_BYTES_FREES
   if (bytes == 0)
     {
-      __libc_free (oldmem); return NULL;
+      __libc_free_core (oldmem); return NULL;
     }
 #endif
 
@@ -3434,7 +3437,7 @@  __libc_realloc (void *oldmem, size_t bytes)
 	return oldmem;
 
       /* Must alloc, copy, free. */
-      newmem = __libc_malloc (bytes);
+      newmem = __libc_malloc_core (bytes);
       if (newmem == NULL)
         return NULL;              /* propagate failure */
 
@@ -3466,7 +3469,7 @@  __libc_realloc (void *oldmem, size_t bytes)
     {
       /* Try harder to allocate memory in other arenas.  */
       LIBC_PROBE (memory_realloc_retry, 2, bytes, oldmem);
-      newp = __libc_malloc (bytes);
+      newp = __libc_malloc_core (bytes);
       if (newp != NULL)
         {
 	  size_t sz = memsize (oldp);
@@ -3478,10 +3481,10 @@  __libc_realloc (void *oldmem, size_t bytes)
 
   return newp;
 }
-libc_hidden_def (__libc_realloc)
+libc_hidden_def (__libc_realloc_core)
 
 void *
-__libc_memalign (size_t alignment, size_t bytes)
+__libc_memalign_core (size_t alignment, size_t bytes)
 {
   /* Round the alignment up to a power of 2.  Reject alignments that overflow
      when rounded up.  Zero alignment is handled by _mid_memalign.  */
@@ -3498,7 +3501,7 @@  __libc_memalign (size_t alignment, size_t bytes)
 
   return _mid_memalign (alignment, bytes);
 }
-libc_hidden_def (__libc_memalign)
+libc_hidden_def (__libc_memalign_core)
 
 /* For ISO C17.  */
 void *
@@ -3551,7 +3554,7 @@  _mid_memalign (size_t alignment, size_t bytes)
 
   /* If we need less alignment than we give anyway, just relay to malloc.  */
   if (alignment <= MALLOC_ALIGNMENT)
-    return __libc_malloc (bytes);
+    return __libc_malloc_core (bytes);
 
 #if USE_TCACHE
   void *victim = tcache_get_align (checked_request2size (bytes), alignment);
@@ -3586,13 +3589,14 @@  _mid_memalign (size_t alignment, size_t bytes)
 }
 
 void *
-__libc_valloc (size_t bytes)
+__libc_valloc_core (size_t bytes)
 {
   return _mid_memalign (GLRO (dl_pagesize), bytes);
 }
+libc_hidden_def (__libc_valloc_core)
 
 void *
-__libc_pvalloc (size_t bytes)
+__libc_pvalloc_core (size_t bytes)
 {
   size_t pagesize = GLRO (dl_pagesize);
   size_t rounded_bytes;
@@ -3607,6 +3611,7 @@  __libc_pvalloc (size_t bytes)
 
   return _mid_memalign (pagesize, rounded_bytes & -pagesize);
 }
+libc_hidden_def (__libc_pvalloc_core)
 
 static void * __attribute_noinline__
 __libc_calloc2 (size_t sz)
@@ -3703,7 +3708,7 @@  __libc_calloc2 (size_t sz)
 }
 
 void *
-__libc_calloc (size_t n, size_t elem_size)
+__libc_calloc_core (size_t n, size_t elem_size)
 {
   size_t bytes;
 
@@ -3747,6 +3752,7 @@  __libc_calloc (size_t n, size_t elem_size)
 #endif
   return __libc_calloc2 (bytes);
 }
+libc_hidden_def (__libc_calloc_core)
 #endif /* IS_IN (libc) */
 
 /*
@@ -4754,12 +4760,13 @@  musable (void *mem)
 
 #if IS_IN (libc)
 size_t
-__malloc_usable_size (void *m)
+__malloc_usable_size_core (void *m)
 {
   if (m == NULL)
     return 0;
   return musable (m);
 }
+libc_hidden_def (__malloc_usable_size_core)
 #endif
 
 /*
@@ -5458,26 +5465,40 @@  __malloc_info (int options, FILE *fp)
 }
 #if IS_IN (libc)
 weak_alias (__malloc_info, malloc_info)
-
-weak_alias (__libc_calloc, calloc)
-strong_alias (__libc_free, free)
-strong_alias (__libc_malloc, malloc)
-weak_alias (__libc_memalign, memalign)
-strong_alias (__libc_realloc, realloc)
-weak_alias (__libc_valloc, valloc)
-weak_alias (__libc_pvalloc, pvalloc)
 weak_alias (__libc_mallinfo, mallinfo)
 weak_alias (__libc_mallinfo2, mallinfo2)
 weak_alias (__libc_mallopt, mallopt)
-
 weak_alias (__malloc_stats, malloc_stats)
-weak_alias (__malloc_usable_size, malloc_usable_size)
 weak_alias (__malloc_trim, malloc_trim)
-#endif
 
-#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
-compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
-#endif
+/* On targets that do not support ifuncs we alias externally visible
+   functions as well as the __libc_* functions to their respective
+   *_core symbols.
+
+   For ifunc prototypes and resolvers see sysdeps/generic/malloc-ifuncs.h.
+ */
+# if !HAVE_IFUNC
+strong_alias (__libc_malloc_core, malloc)
+strong_alias (__libc_malloc_core, __libc_malloc)
+weak_alias (__libc_calloc_core, calloc)
+strong_alias (__libc_calloc_core, __libc_calloc)
+weak_alias (__libc_memalign_core, memalign)
+strong_alias (__libc_memalign_core, __libc_memalign)
+weak_alias (__libc_valloc_core, valloc)
+strong_alias (__libc_valloc_core, __libc_valloc)
+weak_alias (__libc_pvalloc_core, pvalloc)
+strong_alias (__libc_pvalloc_core, __libc_pvalloc)
+strong_alias (__libc_realloc_core, realloc)
+strong_alias (__libc_realloc_core, __libc_realloc)
+#  if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
+compat_symbol (libc, __libc_free_core, cfree, GLIBC_2_0);
+#  endif
+strong_alias (__libc_free_core, free)
+strong_alias (__libc_free_core, __libc_free)
+weak_alias (__malloc_usable_size_core, malloc_usable_size)
+# endif /* !HAVE_IFUNC */
+
+#endif /* IS_IN (libc) */
 
 /* ------------------------------------------------------------
    History:
diff --git a/malloc/tst-check-internal-calls.sh b/malloc/tst-check-internal-calls.sh
new file mode 100644
index 0000000000..3558c15491
--- /dev/null
+++ b/malloc/tst-check-internal-calls.sh
@@ -0,0 +1,53 @@ 
+#!/bin/sh
+# This script checks that malloc.c does not use any external API
+# functions.
+# It is important that whenever an internal function needs to used e.g.
+# __libc_foo() it actually uses __libc_foo_core() symbol. If it uses a
+# non-_core symbol, the returned result may not be suitable for the
+# subsequent use of it internally.
+# The non-_core malloc functions return and accept user-pointers which
+# are different from the internal-pointers that are used by the _core
+# functions.
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+# This script accepts list of source files that need to be grep-ed for
+# certain symbols and the result should be no matches except for the
+# alias declarations
+
+status=0
+
+for src; do
+  echo "checking $src..."
+  for fun in \
+    __libc_malloc \
+    __libc_calloc \
+    __libc_memalign \
+    __libc_valloc \
+    __libc_pvalloc \
+    __libc_realloc \
+    __libc_free; do
+    grep -nw "$fun" $src | egrep -vw "^[0-9]+:(strong_alias|weak_alias)" && {
+      good=$fun"_core"
+      echo "error: code in $src should not use '$fun' (use '$good' instead)"
+      status=1
+    }
+  done
+done
+
+exit $status
diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
index 1f7b01447b..517f44373a 100644
--- a/sysdeps/aarch64/Makefile
+++ b/sysdeps/aarch64/Makefile
@@ -102,6 +102,9 @@  $(objpfx)tst-sme-clone3: $(objpfx)clone3.o $(objpfx)__arm_za_disable.o
 endif
 
 ifeq ($(subdir),malloc)
+sysdep_routines += \
+  malloc-ifuncs \
+  # sysdep_routines
 sysdep_malloc_debug_routines = \
   __mtag_tag_region \
   __mtag_tag_zero_region \
diff --git a/sysdeps/aarch64/malloc-ifuncs.c b/sysdeps/aarch64/malloc-ifuncs.c
new file mode 100644
index 0000000000..9986bab851
--- /dev/null
+++ b/sysdeps/aarch64/malloc-ifuncs.c
@@ -0,0 +1,85 @@ 
+/* Code for ifunc resolvers for malloc: aarch64 version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#if IS_IN (libc)
+
+#include <malloc/malloc-internal.h>
+#include <malloc-ifuncs.h>
+
+/* AArch64-specific resolvers for malloc ifuncs.  */
+
+IFUNC_PROTO (__libc_malloc);
+IFUNC_RESOLVER (__libc_malloc, uint64_t arg0, uint64_t arg1[])
+{
+  return __libc_malloc_core;
+}
+strong_alias (__libc_malloc, malloc)
+
+IFUNC_PROTO (__libc_calloc);
+IFUNC_RESOLVER (__libc_calloc, uint64_t arg0, uint64_t arg1[])
+{
+  return __libc_calloc_core;
+}
+weak_alias (__libc_calloc, calloc)
+
+IFUNC_PROTO (__libc_memalign);
+IFUNC_RESOLVER (__libc_memalign, uint64_t arg0, uint64_t arg1[])
+{
+  return __libc_memalign_core;
+}
+weak_alias (__libc_memalign, memalign)
+
+IFUNC_PROTO (__libc_valloc);
+IFUNC_RESOLVER (__libc_valloc, uint64_t arg0, uint64_t arg1[])
+{
+  return __libc_valloc_core;
+}
+weak_alias (__libc_valloc, valloc)
+
+IFUNC_PROTO (__libc_pvalloc);
+IFUNC_RESOLVER (__libc_pvalloc, uint64_t arg0, uint64_t arg1[])
+{
+  return __libc_pvalloc_core;
+}
+weak_alias (__libc_pvalloc, pvalloc)
+
+IFUNC_PROTO (__libc_realloc);
+IFUNC_RESOLVER (__libc_realloc, uint64_t arg0, uint64_t arg1[])
+{
+  return __libc_realloc_core;
+}
+strong_alias (__libc_realloc, realloc)
+
+IFUNC_PROTO (__libc_free);
+IFUNC_RESOLVER (__libc_free, uint64_t arg0, uint64_t arg1[])
+{
+  return __libc_free_core;
+}
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
+compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
+# endif
+strong_alias (__libc_free, free)
+
+IFUNC_PROTO (__malloc_usable_size);
+IFUNC_RESOLVER (__malloc_usable_size, uint64_t arg0, uint64_t arg1[])
+{
+  return __malloc_usable_size_core;
+}
+weak_alias (__malloc_usable_size, malloc_usable_size)
+
+#endif /* IS_IN (libc) */
diff --git a/sysdeps/generic/Makefile b/sysdeps/generic/Makefile
index 0b586efe63..6816cf907d 100644
--- a/sysdeps/generic/Makefile
+++ b/sysdeps/generic/Makefile
@@ -43,6 +43,9 @@  endif
 endif
 
 ifeq ($(subdir),malloc)
+sysdep_routines += \
+  malloc-ifuncs \
+  # sysdep_routines
 sysdep_malloc_debug_routines += \
   hugepages \
   # sysdep_malloc_debug_routines
diff --git a/sysdeps/generic/malloc-ifuncs.c b/sysdeps/generic/malloc-ifuncs.c
new file mode 100644
index 0000000000..0c03c1466e
--- /dev/null
+++ b/sysdeps/generic/malloc-ifuncs.c
@@ -0,0 +1,94 @@ 
+/* Code for ifunc resolvers for malloc: generic version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#if IS_IN (libc)
+
+#include <malloc/malloc-internal.h>
+#include <malloc-ifuncs.h>
+
+# if HAVE_IFUNC
+
+/* These resolvers are used by default unless overridden by a target.
+   The target-specific resolvers must respect this logic of the default
+   resolvers, replicating this logic where appropriate.
+
+   Any aliases for malloc API functions must be defined here as well
+   and re-defined along with the target-specific resolvers.  */
+
+IFUNC_PROTO (__libc_malloc);
+IFUNC_RESOLVER (__libc_malloc, void)
+{
+  return __libc_malloc_core;
+}
+strong_alias (__libc_malloc, malloc)
+
+IFUNC_PROTO (__libc_calloc);
+IFUNC_RESOLVER (__libc_calloc, void)
+{
+  return __libc_calloc_core;
+}
+weak_alias (__libc_calloc, calloc)
+
+IFUNC_PROTO (__libc_memalign);
+IFUNC_RESOLVER (__libc_memalign, void)
+{
+  return __libc_memalign_core;
+}
+weak_alias (__libc_memalign, memalign)
+
+IFUNC_PROTO (__libc_valloc);
+IFUNC_RESOLVER (__libc_valloc, void)
+{
+  return __libc_valloc_core;
+}
+weak_alias (__libc_valloc, valloc)
+
+IFUNC_PROTO (__libc_pvalloc);
+IFUNC_RESOLVER (__libc_pvalloc, void)
+{
+  return __libc_pvalloc_core;
+}
+weak_alias (__libc_pvalloc, pvalloc)
+
+IFUNC_PROTO (__libc_realloc);
+IFUNC_RESOLVER (__libc_realloc, void)
+{
+  return __libc_realloc_core;
+}
+strong_alias (__libc_realloc, realloc)
+
+IFUNC_PROTO (__libc_free);
+IFUNC_RESOLVER (__libc_free, void)
+{
+  return __libc_free_core;
+}
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_26)
+compat_symbol (libc, __libc_free, cfree, GLIBC_2_0);
+# endif
+strong_alias (__libc_free, free)
+
+IFUNC_PROTO (__malloc_usable_size);
+IFUNC_RESOLVER (__malloc_usable_size, void)
+{
+  return __malloc_usable_size_core;
+}
+weak_alias (__malloc_usable_size, malloc_usable_size)
+
+# endif /* HAVE_IFUNC */
+
+#endif /* IS_IN (libc) */
diff --git a/sysdeps/generic/malloc-ifuncs.h b/sysdeps/generic/malloc-ifuncs.h
new file mode 100644
index 0000000000..1d3ddb0202
--- /dev/null
+++ b/sysdeps/generic/malloc-ifuncs.h
@@ -0,0 +1,61 @@ 
+/* Definitions for ifunc resolvers for malloc: generic version.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _GENERIC_MALLOC_IFUNCS_H
+#define _GENERIC_MALLOC_IFUNCS_H
+
+#if HAVE_IFUNC
+
+#include <stddef.h>
+#include <sys/cdefs.h>
+#include <shlib-compat.h>
+
+/* Core implementations of malloc functions.  An ifunc resolver must
+   use this implementations as a fallback option.  Other implementations
+   may internally call these core function.  */
+void *__libc_malloc_core (size_t);
+libc_hidden_proto (__libc_malloc_core)
+void *__libc_calloc_core (size_t, size_t);
+libc_hidden_proto (__libc_calloc_core)
+void *__libc_memalign_core (size_t, size_t);
+libc_hidden_proto (__libc_memalign_core)
+void *__libc_valloc_core (size_t);
+libc_hidden_proto (__libc_valloc_core)
+void *__libc_pvalloc_core (size_t);
+libc_hidden_proto (__libc_pvalloc_core)
+void *__libc_realloc_core (void *, size_t);
+libc_hidden_proto (__libc_realloc_core)
+void __libc_free_core (void *);
+libc_hidden_proto (__libc_free_core)
+size_t __malloc_usable_size_core (void *);
+libc_hidden_proto (__malloc_usable_size_core)
+
+/* Macros for defining ifunc resolvers for malloc functions.  */
+#define IFUNC_RESOLVER_NAME(fn) fn ## _resolver
+#define STR(x) #x
+#define XSTR(x) STR(x)
+#define IFUNC_PROTO(fn) \
+  __typeof (fn ## _core) fn \
+  __attribute__ ((ifunc (XSTR(IFUNC_RESOLVER_NAME(fn)))))
+#define IFUNC_RESOLVER(fn, ...) \
+  static __attribute_used__ \
+  __typeof (fn ## _core) *IFUNC_RESOLVER_NAME(fn) (__VA_ARGS__)
+
+#endif /* HAVE_IFUNC */
+
+#endif /* _GENERIC_MALLOC_IFUNCS_H */