diff mbox series

binutils: Fix CVE-2025-1148

Message ID 20250319093535.3368863-1-Harish.Sadineni@windriver.com
State New
Headers show
Series binutils: Fix CVE-2025-1148 | expand

Commit Message

Harish Sadineni March 19, 2025, 9:35 a.m. UTC
From: Harish Sadineni <Harish.Sadineni@windriver.com>

A few place dealing with ld script handling made some attempt to free
memory, but this was generally ignored and would be quite a lot of
work to implement.  Instead, use the stat_obstack rather than
mallocing in many more cases.

Backport a patch from upstream to fix CVE-2025-1148
Upstream-Status: Backport [https://sourceware.org/cgit/binutils-gdb/commit/?id=d4115c2c8d447e297ae353892de89192c1996211]

Signed-off-by: Harish Sadineni <Harish.Sadineni@windriver.com>
---
 .../binutils/binutils-2.44.inc                |   1 +
 .../binutils/0016-CVE-2025-1148.patch         | 596 ++++++++++++++++++
 2 files changed, 597 insertions(+)
 create mode 100644 meta/recipes-devtools/binutils/binutils/0016-CVE-2025-1148.patch

Comments

Harish Sadineni March 19, 2025, 9:41 a.m. UTC | #1
In upstream this patch has only been applied to the master branch and hasn't been included in any other branches yet. We've sent the backported patch to the upstream binutils-2_44 branch, but they seem hesitant to merge it into the stable branches.

This is the response I received from the original author( https://sourceware.org/pipermail/binutils/2025-March/139987.html ):
"I deliberately left that patch off the branch, and even now after it has had some time for potential problems to show up I don't think it
warrants backporting.  Memory leaks of this nature hardly qualify as bugs."
Khem Raj March 24, 2025, 7:45 p.m. UTC | #2
On Wed, Mar 19, 2025 at 2:41 AM Sadineni, Harish via
lists.openembedded.org
<Harish.Sadineni=windriver.com@lists.openembedded.org> wrote:
>
> In upstream this patch has only been applied to the master branch and hasn't been included in any other branches yet. We've sent the backported patch to the upstream binutils-2_44 branch, but they seem hesitant to merge it into the stable branches.
>
> This is the response I received from the original author(https://sourceware.org/pipermail/binutils/2025-March/139987.html):
> "I deliberately left that patch off the branch, and even now after it has had some time for potential problems to show up I don't think it
> warrants backporting.  Memory leaks of this nature hardly qualify as bugs."

Thanks for additional context, since Alan is the original author of
the patch these comments weigh in. We should drop this backport from
OE as well since its going to be added maintenance now that upstream
won't have it on release branch

>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#213317): https://lists.openembedded.org/g/openembedded-core/message/213317
> Mute This Topic: https://lists.openembedded.org/mt/111786120/1997914
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [raj.khem@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Mathieu Dubois-Briand March 25, 2025, 7:53 a.m. UTC | #3
On Wed Mar 19, 2025 at 10:35 AM CET, Harish via lists.openembedded.org Sadineni wrote:
> From: Harish Sadineni <Harish.Sadineni@windriver.com>
>
> A few place dealing with ld script handling made some attempt to free
> memory, but this was generally ignored and would be quite a lot of
> work to implement.  Instead, use the stat_obstack rather than
> mallocing in many more cases.
>
> Backport a patch from upstream to fix CVE-2025-1148
> Upstream-Status: Backport [https://sourceware.org/cgit/binutils-gdb/commit/?id=d4115c2c8d447e297ae353892de89192c1996211]
>
> Signed-off-by: Harish Sadineni <Harish.Sadineni@windriver.com>
> ---

Hi,

Thanks for your patch.

I've seen it is already discussed, but as an additional note, current
version seems to generate some build error on the autobuilder:

ERROR: gdb-16.2-r0 do_package_qa: QA Issue: The /usr/share/info/dir file is not meant to be shipped in a particular package. [infodir]

https://autobuilder.yoctoproject.org/valkyrie/#/builders/17/builds/1170
Harish Sadineni March 28, 2025, 5:37 a.m. UTC | #4
On 3/25/2025 1:23 PM, Mathieu Dubois-Briand wrote:
> CAUTION: This email comes from a non Wind River email account!
> Do not click links or open attachments unless you recognize the sender and know the content is safe.
>
> On Wed Mar 19, 2025 at 10:35 AM CET, Harish via lists.openembedded.org Sadineni wrote:
>> From: Harish Sadineni <Harish.Sadineni@windriver.com>
>>
>> A few place dealing with ld script handling made some attempt to free
>> memory, but this was generally ignored and would be quite a lot of
>> work to implement.  Instead, use the stat_obstack rather than
>> mallocing in many more cases.
>>
>> Backport a patch from upstream to fix CVE-2025-1148
>> Upstream-Status: Backport [https://sourceware.org/cgit/binutils-gdb/commit/?id=d4115c2c8d447e297ae353892de89192c1996211]
>>
>> Signed-off-by: Harish Sadineni <Harish.Sadineni@windriver.com>
>> ---
> Hi,
>
> Thanks for your patch.
>
> I've seen it is already discussed, but as an additional note, current
> version seems to generate some build error on the autobuilder:
>
> ERROR: gdb-16.2-r0 do_package_qa: QA Issue: The /usr/share/info/dir file is not meant to be shipped in a particular package. [infodir]
>
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/17/builds/1170
>
> I tested "bitbake world" with the same configuration as in qemux86-world-alt with the current patch, and the issue did not occur.
> Typically, errors like "file is not meant to be shipped in a particular package" arise when there is a change in the recipe
> related to installation or packaging.
>
> Thanks,
> Harish
>
> --
> Mathieu Dubois-Briand, Bootlin
> Embedded Linux and Kernel engineering
> https://bootlin.com
>
Mathieu Dubois-Briand March 28, 2025, 12:41 p.m. UTC | #5
On Fri Mar 28, 2025 at 6:50 AM CET, Harish via lists.openembedded.org Sadineni wrote:
> [Edited Message Follows]
>
>
> On 3/25/2025 1:23 PM, Mathieu Dubois-Briand wrote:
> > CAUTION: This email comes from a non Wind River email account!
> > Do not click links or open attachments unless you recognize the sender and know the content is safe.
> >
> > On Wed Mar 19, 2025 at 10:35 AM CET, Harish via lists.openembedded.org Sadineni wrote:
> >> From: Harish Sadineni <Harish.Sadineni@windriver.com>
> >>
> >> A few place dealing with ld script handling made some attempt to free
> >> memory, but this was generally ignored and would be quite a lot of
> >> work to implement.  Instead, use the stat_obstack rather than
> >> mallocing in many more cases.
> >>
> >> Backport a patch from upstream to fix CVE-2025-1148
> >> Upstream-Status: Backport [https://sourceware.org/cgit/binutils-gdb/commit/?id=d4115c2c8d447e297ae353892de89192c1996211]
> >>
> >> Signed-off-by: Harish Sadineni <Harish.Sadineni@windriver.com>
> >> ---
> > Hi,
> >
> > Thanks for your patch.
> >
> > I've seen it is already discussed, but as an additional note, current
> > version seems to generate some build error on the autobuilder:
> >
> > ERROR: gdb-16.2-r0 do_package_qa: QA Issue: The /usr/share/info/dir file is not meant to be shipped in a particular package. [infodir]
> >
> > https://autobuilder.yoctoproject.org/valkyrie/#/builders/17/builds/1170
> >
>  I tested "bitbake world" with the same configuration as in qemux86-world-alt with the current patch, and the issue did not occur.
> Typically, errors like "file is not meant to be shipped in a particular package" arise when there is a change in the recipe
> related to installation or packaging.
>

I relaunched the build and it did fail again, twice:

https://autobuilder.yoctoproject.org/valkyrie/#/builders/17/builds/1170
https://autobuilder.yoctoproject.org/valkyrie/#/builders/17/builds/1206

But on the other hand, I confirm I was not able to reproduce it locally
event using the exact same commit as on the AB.

It will try to apply it again on top of my master-next and see how it
goes
diff mbox series

Patch

diff --git a/meta/recipes-devtools/binutils/binutils-2.44.inc b/meta/recipes-devtools/binutils/binutils-2.44.inc
index 1aafbd5285..7b376d3676 100644
--- a/meta/recipes-devtools/binutils/binutils-2.44.inc
+++ b/meta/recipes-devtools/binutils/binutils-2.44.inc
@@ -36,5 +36,6 @@  SRC_URI = "\
      file://0013-Define-alignof-using-_Alignof-when-using-C11-or-newe.patch \
      file://0014-Remove-duplicate-pe-dll.o-entry-deom-targ_extra_ofil.patch \
      file://0015-CVE-2025-1153.patch \
+     file://0016-CVE-2025-1148.patch \
 "
 S  = "${WORKDIR}/git"
diff --git a/meta/recipes-devtools/binutils/binutils/0016-CVE-2025-1148.patch b/meta/recipes-devtools/binutils/binutils/0016-CVE-2025-1148.patch
new file mode 100644
index 0000000000..5d8b947431
--- /dev/null
+++ b/meta/recipes-devtools/binutils/binutils/0016-CVE-2025-1148.patch
@@ -0,0 +1,596 @@ 
+From d4115c2c8d447e297ae353892de89192c1996211 Mon Sep 17 00:00:00 2001
+From: Alan Modra <amodra@gmail.com>
+Date: Sat, 11 Jan 2025 16:19:09 +1030
+Subject: [PATCH] Replace xmalloc with stat_alloc in ld parser
+
+A few place dealing with ld script handling made some attempt to free
+memory, but this was generally ignored and would be quite a lot of
+work to implement.  Instead, use the stat_obstack rather than
+mallocing in many more cases.
+
+        * ldexp.c (exp_get_fill): Use stat_alloc for fill.
+        * ldfile.c (ldfile_try_open_bfd): Don't free yylval fields.
+        * ldgram.y: Replace xmalloc with stat_alloc throughout.
+        * ldlang.c (stat_memdup, stat_strdup): New functions.
+        (ldirname): Use stat_memdup.  Don't strdup ".".
+        (output_section_callback_sort): Use stat_alloc.
+        (output_section_callback_tree_to_list): Don't free.
+        (lang_memory_region_lookup): Use stat_strdup.
+        (lang_memory_region_alias): Likewise.
+        (add_excluded_libs): Use stat_alloc and stat_memdup.
+        (ldlang_add_undef, ldlang_add_require_defined): Use stat_strdup.
+        (lang_add_nocrossref, lang_leave_overlay): Use stat_alloc.
+        (realsymbol): Use stat_strdup for return value and always
+        free symbol.
+        (lang_new_vers_pattern, lang_new_vers_node): Use stat_alloc.
+        (lang_finalize_version_expr_head): Don't free.  Delete FIXME.
+        (lang_register_vers_node): Don't free.
+        (lang_add_vers_depend): Use stat_alloc.
+        (lang_do_version_exports_section): Likewise.
+        (lang_add_unique): Use stat_alloc and stat_strdup.
+        (lang_append_dynamic_list): Use stat_alloc.
+        * ldlang.h (stat_memdup, stat_strdup): Declare.
+        * ldlex.l: Replace xstrdup with stat_strdup throughout.
+        Replace xmemdup with stat_memdup too.
+        * lexsup.c (parse_args): Don't free export list or dynamic
+        list.
+
+(cherry picked from commit: d4115c2c8d447e297ae353892de89192c1996211)
+
+Upstream-Status: Backport [https://sourceware.org/cgit/binutils-gdb/commit/?id=d4115c2c8d447e297ae353892de89192c1996211]
+CVE: CVE-2025-1148
+
+Signed-off-by: Harish Sadineni <Harish.Sadineni@windriver.com>
+
+---
+ ld/ldexp.c  |  4 +--
+ ld/ldfile.c | 17 ++---------
+ ld/ldgram.y | 21 ++++++-------
+ ld/ldlang.c | 87 ++++++++++++++++++++++++++---------------------------
+ ld/ldlang.h |  4 +++
+ ld/ldlex.l  | 23 +++++++-------
+ ld/lexsup.c | 22 +-------------
+ 7 files changed, 74 insertions(+), 104 deletions(-)
+
+diff --git a/ld/ldexp.c b/ld/ldexp.c
+index 035cef60448..87d882e5066 100644
+--- a/ld/ldexp.c
++++ b/ld/ldexp.c
+@@ -1630,7 +1630,7 @@ exp_get_fill (etree_type *tree, fill_type *def, char *name)
+     {
+       unsigned char *dst;
+       unsigned char *s;
+-      fill = (fill_type *) xmalloc ((len + 1) / 2 + sizeof (*fill) - 1);
++      fill = stat_alloc ((len + 1) / 2 + sizeof (*fill) - 1);
+       fill->size = (len + 1) / 2;
+       dst = fill->data;
+       s = (unsigned char *) expld.result.str;
+@@ -1655,7 +1655,7 @@ exp_get_fill (etree_type *tree, fill_type *def, char *name)
+     }
+   else
+     {
+-      fill = (fill_type *) xmalloc (4 + sizeof (*fill) - 1);
++      fill = stat_alloc (4 + sizeof (*fill) - 1);
+       val = expld.result.value;
+       fill->data[0] = (val >> 24) & 0xff;
+       fill->data[1] = (val >> 16) & 0xff;
+diff --git a/ld/ldfile.c b/ld/ldfile.c
+index 12551504ae6..404af5fba14 100644
+--- a/ld/ldfile.c
++++ b/ld/ldfile.c
+@@ -438,18 +438,11 @@ ldfile_try_open_bfd (const char *attempt,
+ 			  if (token == ',')
+ 			    {
+ 			      if ((token = yylex ()) != NAME)
+-				{
+-				  free (arg1);
+-				  continue;
+-				}
++				continue;
+ 			      arg2 = yylval.name;
+ 			      if ((token = yylex ()) != ','
+ 				  || (token = yylex ()) != NAME)
+-				{
+-				  free (arg1);
+-				  free (arg2);
+-				  continue;
+-				}
++				continue;
+ 			      arg3 = yylval.name;
+ 			      token = yylex ();
+ 			    }
+@@ -468,18 +461,12 @@ ldfile_try_open_bfd (const char *attempt,
+ 			      if (strcmp (arg, lang_get_output_target ()) != 0)
+ 				skip = 1;
+ 			    }
+-			  free (arg1);
+-			  free (arg2);
+-			  free (arg3);
+ 			  break;
+ 			case NAME:
+ 			case LNAME:
+ 			case VERS_IDENTIFIER:
+ 			case VERS_TAG:
+-			  free (yylval.name);
+-			  break;
+ 			case INT:
+-			  free (yylval.bigint.str);
+ 			  break;
+ 			}
+ 		      token = yylex ();
+diff --git a/ld/ldgram.y b/ld/ldgram.y
+index 6d516db38c7..9bb98de2f0a 100644
+--- a/ld/ldgram.y
++++ b/ld/ldgram.y
+@@ -543,7 +543,7 @@ section_name_spec:
+ sect_flag_list:	NAME
+ 			{
+ 			  struct flag_info_list *n;
+-			  n = ((struct flag_info_list *) xmalloc (sizeof *n));
++			  n = stat_alloc (sizeof *n);
+ 			  if ($1[0] == '!')
+ 			    {
+ 			      n->with = without_flags;
+@@ -561,7 +561,7 @@ sect_flag_list:	NAME
+ 	|	sect_flag_list '&' NAME
+ 			{
+ 			  struct flag_info_list *n;
+-			  n = ((struct flag_info_list *) xmalloc (sizeof *n));
++			  n = stat_alloc (sizeof *n);
+ 			  if ($3[0] == '!')
+ 			    {
+ 			      n->with = without_flags;
+@@ -582,7 +582,7 @@ sect_flags:
+ 		INPUT_SECTION_FLAGS '(' sect_flag_list ')'
+ 			{
+ 			  struct flag_info *n;
+-			  n = ((struct flag_info *) xmalloc (sizeof *n));
++			  n = stat_alloc (sizeof *n);
+ 			  n->flag_list = $3;
+ 			  n->flags_initialized = false;
+ 			  n->not_with_flags = 0;
+@@ -595,7 +595,7 @@ exclude_name_list:
+ 		exclude_name_list wildcard_name
+ 			{
+ 			  struct name_list *tmp;
+-			  tmp = (struct name_list *) xmalloc (sizeof *tmp);
++			  tmp = stat_alloc (sizeof *tmp);
+ 			  tmp->name = $2;
+ 			  tmp->next = $1;
+ 			  $$ = tmp;
+@@ -604,7 +604,7 @@ exclude_name_list:
+ 		wildcard_name
+ 			{
+ 			  struct name_list *tmp;
+-			  tmp = (struct name_list *) xmalloc (sizeof *tmp);
++			  tmp = stat_alloc (sizeof *tmp);
+ 			  tmp->name = $1;
+ 			  tmp->next = NULL;
+ 			  $$ = tmp;
+@@ -615,7 +615,7 @@ section_name_list:
+ 		section_name_list opt_comma section_name_spec
+ 			{
+ 			  struct wildcard_list *tmp;
+-			  tmp = (struct wildcard_list *) xmalloc (sizeof *tmp);
++			  tmp = stat_alloc (sizeof *tmp);
+ 			  tmp->next = $1;
+ 			  tmp->spec = $3;
+ 			  $$ = tmp;
+@@ -624,7 +624,7 @@ section_name_list:
+ 		section_name_spec
+ 			{
+ 			  struct wildcard_list *tmp;
+-			  tmp = (struct wildcard_list *) xmalloc (sizeof *tmp);
++			  tmp = stat_alloc (sizeof *tmp);
+ 			  tmp->next = NULL;
+ 			  tmp->spec = $1;
+ 			  $$ = tmp;
+@@ -926,7 +926,7 @@ nocrossref_list:
+ 		{
+ 		  struct lang_nocrossref *n;
+ 
+-		  n = (struct lang_nocrossref *) xmalloc (sizeof *n);
++		  n = stat_alloc (sizeof *n);
+ 		  n->name = $1;
+ 		  n->next = $2;
+ 		  $$ = n;
+@@ -935,7 +935,7 @@ nocrossref_list:
+ 		{
+ 		  struct lang_nocrossref *n;
+ 
+-		  n = (struct lang_nocrossref *) xmalloc (sizeof *n);
++		  n = stat_alloc (sizeof *n);
+ 		  n->name = $1;
+ 		  n->next = $3;
+ 		  $$ = n;
+@@ -1225,8 +1225,7 @@ phdr_opt:
+ 		{
+ 		  struct lang_output_section_phdr_list *n;
+ 
+-		  n = ((struct lang_output_section_phdr_list *)
+-		       xmalloc (sizeof *n));
++		  n = stat_alloc (sizeof *n);
+ 		  n->name = $3;
+ 		  n->used = false;
+ 		  n->next = $1;
+diff --git a/ld/ldlang.c b/ld/ldlang.c
+index 74c0271973f..f613fc91f0a 100644
+--- a/ld/ldlang.c
++++ b/ld/ldlang.c
+@@ -188,6 +188,23 @@ stat_alloc (size_t size)
+   return obstack_alloc (&stat_obstack, size);
+ }
+ 
++void *
++stat_memdup (const void *src, size_t copy_size, size_t alloc_size)
++{
++  void *ret = obstack_alloc (&stat_obstack, alloc_size);
++  memcpy (ret, src, copy_size);
++  if (alloc_size > copy_size)
++    memset ((char *) ret + copy_size, 0, alloc_size - copy_size);
++  return ret;
++}
++
++char *
++stat_strdup (const char *str)
++{
++  size_t len = strlen (str) + 1;
++  return stat_memdup (str, len, len);
++}
++
+ /* Code for handling simple wildcards without going through fnmatch,
+    which can be expensive because of charset translations etc.  */
+ 
+@@ -277,15 +294,13 @@ static char *
+ ldirname (const char *name)
+ {
+   const char *base = lbasename (name);
+-  char *dirname;
+ 
+   while (base > name && IS_DIR_SEPARATOR (base[-1]))
+     --base;
+-  if (base == name)
+-    return strdup (".");
+-  dirname = strdup (name);
+-  dirname[base - name] = '\0';
+-  return dirname;
++  size_t len = base - name;
++  if (len == 0)
++    return ".";
++  return stat_memdup (name, len, len + 1);
+ }
+ 
+ /* If PATTERN is of the form archive:file, return a pointer to the
+@@ -733,7 +748,7 @@ output_section_callback_sort (lang_wild_statement_type *ptr,
+   if (wont_add_section_p (section, os))
+     return;
+ 
+-  node = (lang_section_bst_type *) xmalloc (sizeof (lang_section_bst_type));
++  node = stat_alloc (sizeof (*node));
+   node->left = 0;
+   node->right = 0;
+   node->section = section;
+@@ -764,8 +779,6 @@ output_section_callback_tree_to_list (lang_wild_statement_type *ptr,
+ 
+   if (tree->right)
+     output_section_callback_tree_to_list (ptr, tree->right, output);
+-
+-  free (tree);
+ }
+ 
+ 
+@@ -1454,7 +1467,7 @@ lang_memory_region_lookup (const char *const name, bool create)
+ 
+   new_region = stat_alloc (sizeof (lang_memory_region_type));
+ 
+-  new_region->name_list.name = xstrdup (name);
++  new_region->name_list.name = stat_strdup (name);
+   new_region->name_list.next = NULL;
+   new_region->next = NULL;
+   new_region->origin_exp = NULL;
+@@ -1509,7 +1522,7 @@ lang_memory_region_alias (const char *alias, const char *region_name)
+ 
+   /* Add alias to region name list.  */
+   n = stat_alloc (sizeof (lang_memory_region_name));
+-  n->name = xstrdup (alias);
++  n->name = stat_strdup (alias);
+   n->next = region->name_list.next;
+   region->name_list.next = n;
+ }
+@@ -2989,11 +3002,9 @@ add_excluded_libs (const char *list)
+       end = strpbrk (p, ",:");
+       if (end == NULL)
+ 	end = p + strlen (p);
+-      entry = (struct excluded_lib *) xmalloc (sizeof (*entry));
++      entry = stat_alloc (sizeof (*entry));
+       entry->next = excluded_libs;
+-      entry->name = (char *) xmalloc (end - p + 1);
+-      memcpy (entry->name, p, end - p);
+-      entry->name[end - p] = '\0';
++      entry->name = stat_memdup (p, end - p, end - p + 1);
+       excluded_libs = entry;
+       if (*end == '\0')
+ 	break;
+@@ -4017,7 +4028,7 @@ ldlang_add_undef (const char *const name, bool cmdline ATTRIBUTE_UNUSED)
+   new_undef->next = ldlang_undef_chain_list_head;
+   ldlang_undef_chain_list_head = new_undef;
+ 
+-  new_undef->name = xstrdup (name);
++  new_undef->name = stat_strdup (name);
+ 
+   if (link_info.output_bfd != NULL)
+     insert_undefined (new_undef->name);
+@@ -4096,7 +4107,7 @@ ldlang_add_require_defined (const char *const name)
+   ldlang_add_undef (name, true);
+   ptr = stat_alloc (sizeof (*ptr));
+   ptr->next = require_defined_symbol_list;
+-  ptr->name = strdup (name);
++  ptr->name = stat_strdup (name);
+   require_defined_symbol_list = ptr;
+ }
+ 
+@@ -9183,7 +9194,7 @@ lang_add_nocrossref (lang_nocrossref_type *l)
+ {
+   struct lang_nocrossrefs *n;
+ 
+-  n = (struct lang_nocrossrefs *) xmalloc (sizeof *n);
++  n = stat_alloc (sizeof *n);
+   n->next = nocrossref_list;
+   n->list = l;
+   n->onlyfirst = false;
+@@ -9366,7 +9377,7 @@ lang_leave_overlay (etree_type *lma_expr,
+ 	{
+ 	  lang_nocrossref_type *nc;
+ 
+-	  nc = (lang_nocrossref_type *) xmalloc (sizeof *nc);
++	  nc = stat_alloc (sizeof *nc);
+ 	  nc->name = l->os->name;
+ 	  nc->next = nocrossref;
+ 	  nocrossref = nc;
+@@ -9546,13 +9557,10 @@ realsymbol (const char *pattern)
+   if (changed)
+     {
+       *s = '\0';
+-      return symbol;
+-    }
+-  else
+-    {
+-      free (symbol);
+-      return pattern;
++      pattern = stat_strdup (symbol);
+     }
++  free (symbol);
++  return pattern;
+ }
+ 
+ /* This is called for each variable name or match expression.  NEW_NAME is
+@@ -9567,7 +9575,7 @@ lang_new_vers_pattern (struct bfd_elf_version_expr *orig,
+ {
+   struct bfd_elf_version_expr *ret;
+ 
+-  ret = (struct bfd_elf_version_expr *) xmalloc (sizeof *ret);
++  ret = stat_alloc (sizeof *ret);
+   ret->next = orig;
+   ret->symver = 0;
+   ret->script = 0;
+@@ -9604,7 +9612,8 @@ lang_new_vers_node (struct bfd_elf_version_expr *globals,
+ {
+   struct bfd_elf_version_tree *ret;
+ 
+-  ret = (struct bfd_elf_version_tree *) xcalloc (1, sizeof *ret);
++  ret = stat_alloc (sizeof (*ret));
++  memset (ret, 0, sizeof (*ret));
+   ret->globals.list = globals;
+   ret->locals.list = locals;
+   ret->match = lang_vers_match;
+@@ -9686,15 +9695,7 @@ lang_finalize_version_expr_head (struct bfd_elf_version_expr_head *head)
+ 		    }
+ 		  while (e1 && strcmp (e1->pattern, e->pattern) == 0);
+ 
+-		  if (last == NULL)
+-		    {
+-		      /* This is a duplicate.  */
+-		      /* FIXME: Memory leak.  Sometimes pattern is not
+-			 xmalloced alone, but in larger chunk of memory.  */
+-		      /* free (e->pattern); */
+-		      free (e);
+-		    }
+-		  else
++		  if (last != NULL)
+ 		    {
+ 		      e->next = last->next;
+ 		      last->next = e;
+@@ -9734,7 +9735,6 @@ lang_register_vers_node (const char *name,
+     {
+       einfo (_("%X%P: anonymous version tag cannot be combined"
+ 	       " with other version tags\n"));
+-      free (version);
+       return;
+     }
+ 
+@@ -9827,7 +9827,7 @@ lang_add_vers_depend (struct bfd_elf_version_deps *list, const char *name)
+   struct bfd_elf_version_deps *ret;
+   struct bfd_elf_version_tree *t;
+ 
+-  ret = (struct bfd_elf_version_deps *) xmalloc (sizeof *ret);
++  ret = stat_alloc (sizeof *ret);
+   ret->next = list;
+ 
+   for (t = link_info.version_info; t != NULL; t = t->next)
+@@ -9860,7 +9860,7 @@ lang_do_version_exports_section (void)
+ 	continue;
+ 
+       len = sec->size;
+-      contents = (char *) xmalloc (len);
++      contents = stat_alloc (len);
+       if (!bfd_get_section_contents (is->the_bfd, sec, contents, 0, len))
+ 	einfo (_("%X%P: unable to read .exports section contents\n"), sec);
+ 
+@@ -9871,8 +9871,6 @@ lang_do_version_exports_section (void)
+ 	  p = strchr (p, '\0') + 1;
+ 	}
+ 
+-      /* Do not free the contents, as we used them creating the regex.  */
+-
+       /* Do not include this section in the link.  */
+       sec->flags |= SEC_EXCLUDE | SEC_KEEP;
+     }
+@@ -9936,8 +9934,8 @@ lang_add_unique (const char *name)
+     if (strcmp (ent->name, name) == 0)
+       return;
+ 
+-  ent = (struct unique_sections *) xmalloc (sizeof *ent);
+-  ent->name = xstrdup (name);
++  ent = stat_alloc (sizeof *ent);
++  ent->name = stat_strdup (name);
+   ent->next = unique_section_list;
+   unique_section_list = ent;
+ }
+@@ -9960,7 +9958,8 @@ lang_append_dynamic_list (struct bfd_elf_dynamic_list **list_p,
+     {
+       struct bfd_elf_dynamic_list *d;
+ 
+-      d = (struct bfd_elf_dynamic_list *) xcalloc (1, sizeof *d);
++      d = stat_alloc (sizeof (*d));
++      memset (d, 0, sizeof (*d));
+       d->head.list = dynamic;
+       d->match = lang_vers_match;
+       *list_p = d;
+diff --git a/ld/ldlang.h b/ld/ldlang.h
+index 91779a584b4..b074d6f5e37 100644
+--- a/ld/ldlang.h
++++ b/ld/ldlang.h
+@@ -664,6 +664,10 @@ extern void lang_for_each_statement_worker
+   (void (*) (lang_statement_union_type *), lang_statement_union_type *);
+ extern void *stat_alloc
+   (size_t);
++extern void * stat_memdup
++  (const void *, size_t, size_t);
++extern char *stat_strdup
++  (const char *);
+ extern void strip_excluded_output_sections
+   (void);
+ extern void lang_clear_os_map
+diff --git a/ld/ldlex.l b/ld/ldlex.l
+index e704a979722..58eca1b2fe7 100644
+--- a/ld/ldlex.l
++++ b/ld/ldlex.l
+@@ -188,7 +188,8 @@ V_IDENTIFIER [*?.$_a-zA-Z\[\]\-\!\^\\]([*?.$_a-zA-Z0-9\[\]\-\!\^\\]|::)*
+ 					   && (yytext[1] == 'x'
+ 					       || yytext[1] == 'X'))
+ 				    {
+-				      yylval.bigint.str = xstrdup (yytext + 2);
++				      yylval.bigint.str
++					= stat_strdup (yytext + 2);
+ 				    }
+ 				  return INT;
+ 				}
+@@ -391,32 +392,32 @@ V_IDENTIFIER [*?.$_a-zA-Z\[\]\-\!\^\\]([*?.$_a-zA-Z0-9\[\]\-\!\^\\]|::)*
+ 
+ <MRI>{FILENAMECHAR1}{NOCFILENAMECHAR}*	{
+ /* Filename without commas, needed to parse mri stuff */
+-				  yylval.name = xstrdup (yytext);
++				  yylval.name = stat_strdup (yytext);
+ 				  return NAME;
+ 				}
+ 
+ 
+ <SCRIPT,INPUTLIST>{FILENAMECHAR1}{FILENAMECHAR}*	{
+-				  yylval.name = xstrdup (yytext);
++				  yylval.name = stat_strdup (yytext);
+ 				  return NAME;
+ 				}
+ <INPUTLIST>"="{FILENAMECHAR1}{FILENAMECHAR}*	{
+ /* Filename to be prefixed by --sysroot or when non-sysrooted, nothing.  */
+-				  yylval.name = xstrdup (yytext);
++				  yylval.name = stat_strdup (yytext);
+ 				  return NAME;
+ 				}
+ <INPUTLIST>"-l"{FILENAMECHAR}+ {
+-				  yylval.name = xstrdup (yytext + 2);
++				  yylval.name = stat_strdup (yytext + 2);
+ 				  return LNAME;
+ 				}
+ <EXPRESSION>{SYMBOLNAMECHAR1}{SYMBOLNAMECHAR}* {
+-				  yylval.name = xstrdup (yytext);
++				  yylval.name = stat_strdup (yytext);
+ 				  return NAME;
+ 				}
+   /* The following rule is to prevent a fill expression on the output
+      section before /DISCARD/ interpreting the '/' as a divide.  */
+ <EXPRESSION>"/DISCARD/"		{
+-				  yylval.name = xstrdup (yytext);
++				  yylval.name = stat_strdup (yytext);
+ 				  return NAME;
+ 				}
+ <WILD>{WILDCHAR}* {
+@@ -431,14 +432,14 @@ V_IDENTIFIER [*?.$_a-zA-Z\[\]\-\!\^\\]([*?.$_a-zA-Z0-9\[\]\-\!\^\\]|::)*
+ 		  }
+ 		else
+ 		  {
+-		    yylval.name = xstrdup (yytext);
++		    yylval.name = stat_strdup (yytext);
+ 		    return NAME;
+ 		  }
+ 	}
+ 
+ <SCRIPT,EXPRESSION,WILD,VERS_NODE,INPUTLIST>"\""[^\"]*"\"" {
+ 		/* No matter the state, quotes give what's inside.  */
+-		yylval.name = xmemdup (yytext + 1, yyleng - 2, yyleng - 1);
++		yylval.name = stat_memdup (yytext + 1, yyleng - 2, yyleng - 1);
+ 		return NAME;
+ 	}
+ 
+@@ -457,10 +458,10 @@ V_IDENTIFIER [*?.$_a-zA-Z\[\]\-\!\^\\]([*?.$_a-zA-Z0-9\[\]\-\!\^\\]|::)*
+ 
+ <VERS_NODE>extern		{ RTOKEN(EXTERN); }
+ 
+-<VERS_NODE>{V_IDENTIFIER}	{ yylval.name = xstrdup (yytext);
++<VERS_NODE>{V_IDENTIFIER}	{ yylval.name = stat_strdup (yytext);
+ 				  return VERS_IDENTIFIER; }
+ 
+-<VERS_SCRIPT>{V_TAG}		{ yylval.name = xstrdup (yytext);
++<VERS_SCRIPT>{V_TAG}		{ yylval.name = stat_strdup (yytext);
+ 				  return VERS_TAG; }
+ 
+ <VERS_START>"{"			{ BEGIN(VERS_SCRIPT); return *yytext; }
+diff --git a/ld/lexsup.c b/ld/lexsup.c
+index 5399aa45b72..58b9bdd4974 100644
+--- a/ld/lexsup.c
++++ b/ld/lexsup.c
+@@ -1989,16 +1989,6 @@ parse_args (unsigned argc, char **argv)
+ 	  if (opt_dynamic_list != dynamic_list_data)
+ 	    opt_dynamic_list = dynamic_list;
+ 	}
+-      else
+-	{
+-	  /* Free the export list.  */
+-	  for (; head->next != NULL; head = next)
+-	    {
+-	      next = head->next;
+-	      free (head);
+-	    }
+-	  free (export_list);
+-	}
+     }
+ 
+   switch (opt_dynamic_list)
+@@ -2022,17 +2012,7 @@ parse_args (unsigned argc, char **argv)
+ 	break;
+       case symbolic:
+ 	link_info.symbolic = true;
+-	if (link_info.dynamic_list)
+-	  {
+-	    struct bfd_elf_version_expr *ent, *next;
+-	    for (ent = link_info.dynamic_list->head.list; ent; ent = next)
+-	      {
+-		next = ent->next;
+-		free (ent);
+-	      }
+-	    free (link_info.dynamic_list);
+-	    link_info.dynamic_list = NULL;
+-	  }
++	link_info.dynamic_list = NULL;
+ 	break;
+       case symbolic_functions:
+ 	link_info.dynamic = true;