convert_data would set the libelf errno on malloc failure and returned
without populating scn->data_list.  __libelf_set_data_list_rdlock then
unconditionally set data_list_rear to the empty node, so elf_getdata
returned a zeroed Elf_Data without error and elf_newdata returned a
"valid" empty node.  Worse, the resync in elf_newdata (data_read &&
rawdata_base != NULL && data_list_rear == NULL) can never run again
once data_list_rear is set, so the section's file data was permanently
dropped from the data list.

Make convert_data and __libelf_set_data_list_rdlock return a status
and do not set data_list_rear on failure.  Propagate the failure to
all callers. elf_getdata and elf_newdata now return NULL with
ELF_E_NOMEM, and __libelf_reset_rawdata (used by elf_compress and
elf_gnu_compress) returns a status so those report the failure
instead of success.  On failure the section is left with rawdata
installed and data_list_rear NULL, which is a consistent retryable
state.

        * libelf/elf_getdata.c (convert_data): Return 1 on malloc
        failure, 0 on success.
        (__libelf_set_data_list_rdlock): Return a status, do not set
        data_list_rear on failure.
        (__elf_getdata_rdlock): Return NULL on conversion failure.
        * libelf/elf_newdata.c (elf_newdata): Propagate the
        __libelf_set_data_list_rdlock failure.
        * libelf/elf_compress.c (__libelf_reset_rawdata): Return a
        status.
        (elf_compress): Propagate the __libelf_reset_rawdata failure.
        * libelf/elf_compress_gnu.c (elf_gnu_compress): Likewise.
        * libelf/libelfP.h (__libelf_set_data_list_rdlock): Update the
        declarations.
        (__libelf_reset_rawdata): Likewise.

Signed-off-by: Mark Wielaard <[email protected]>
---
 libelf/elf_compress.c     | 24 ++++++++++++++---------
 libelf/elf_compress_gnu.c | 11 +++++++----
 libelf/elf_getdata.c      | 41 +++++++++++++++++++++++----------------
 libelf/elf_newdata.c      |  9 +++++----
 libelf/libelfP.h          | 15 ++++++++------
 5 files changed, 60 insertions(+), 40 deletions(-)

diff --git a/libelf/elf_compress.c b/libelf/elf_compress.c
index bb341dda0fda..6f042224d7e0 100644
--- a/libelf/elf_compress.c
+++ b/libelf/elf_compress.c
@@ -1,6 +1,6 @@
 /* Compress or decompress a section.
    Copyright (C) 2015, 2016 Red Hat, Inc.
-   Copyright (C) 2023, Mark J. Wielaard <[email protected]>
+   Copyright (C) 2023, 2026 Mark J. Wielaard <[email protected]>
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -553,8 +553,9 @@ __libelf_decompress_elf (Elf_Scn *scn, size_t *size_out, 
size_t *addralign)
   return decompress_elf (scn, size_out, addralign, false);
 }
 
-/* Assumes buf is a malloced buffer.  */
-void
+/* Assumes buf is a malloced buffer.  Returns 1 on failure
+   (out of memory), 0 on success.  */
+int
 internal_function
 __libelf_reset_rawdata (Elf_Scn *scn, void *buf, size_t size, size_t align,
                        Elf_Type type)
@@ -604,7 +605,10 @@ __libelf_reset_rawdata (Elf_Scn *scn, void *buf, size_t 
size, size_t align,
      data (might have to convert the Chdr to native format).  */
   scn->data_read = 1;
   scn->flags |= ELF_F_FILEDATA;
-  __libelf_set_data_list_rdlock (scn, 1);
+  if (__libelf_set_data_list_rdlock (scn, 1) != 0)
+    return 1;
+
+  return 0;
 }
 
 int
@@ -742,7 +746,8 @@ elf_compress (Elf_Scn *scn, int type, unsigned int flags)
          shdr.s64->sh_flags |= SHF_COMPRESSED;
        }
 
-      __libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_CHDR);
+      if (__libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_CHDR) != 0)
+       return -1;
 
       /* The section is now compressed, we could keep the uncompressed
         data around, but since that might have been multiple Elf_Data
@@ -792,10 +797,11 @@ elf_compress (Elf_Scn *scn, int type, unsigned int flags)
          shdr.s64->sh_flags &= ~SHF_COMPRESSED;
        }
 
-      __libelf_reset_rawdata (scn, scn->zdata_base,
-                             scn->zdata_size, scn->zdata_align,
-                             __libelf_data_type (&ehdr, sh_type,
-                                                 scn->zdata_align));
+      if (__libelf_reset_rawdata (scn, scn->zdata_base,
+                                 scn->zdata_size, scn->zdata_align,
+                                 __libelf_data_type (&ehdr, sh_type,
+                                                     scn->zdata_align)) != 0)
+       return -1;
 
       return 1;
     }
diff --git a/libelf/elf_compress_gnu.c b/libelf/elf_compress_gnu.c
index a9fff07edc2d..ff080c7c2429 100644
--- a/libelf/elf_compress_gnu.c
+++ b/libelf/elf_compress_gnu.c
@@ -1,5 +1,6 @@
 /* Compress or decompress a section.
    Copyright (C) 2015 Red Hat, Inc.
+   Copyright (C) 2026 Mark J. Wielaard <[email protected]>
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -131,7 +132,8 @@ elf_compress_gnu (Elf_Scn *scn, int inflate, unsigned int 
flags)
       else
          shdr.s64->sh_size = new_size;
 
-      __libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_BYTE);
+      if (__libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_BYTE) != 0)
+       return -1;
 
       /* The section is now compressed, we could keep the uncompressed
         data around, but since that might have been multiple Elf_Data
@@ -193,9 +195,10 @@ elf_compress_gnu (Elf_Scn *scn, int inflate, unsigned int 
flags)
       else
        shdr.s64->sh_size = size;
 
-      __libelf_reset_rawdata (scn, buf_out, size, sh_addralign,
-                             __libelf_data_type (&ehdr, sh_type,
-                                                 sh_addralign));
+      if (__libelf_reset_rawdata (scn, buf_out, size, sh_addralign,
+                                 __libelf_data_type (&ehdr, sh_type,
+                                                     sh_addralign)) != 0)
+       return -1;
 
       scn->zdata_base = buf_out;
 
diff --git a/libelf/elf_getdata.c b/libelf/elf_getdata.c
index 8afc8c6ff864..2e05ee976998 100644
--- a/libelf/elf_getdata.c
+++ b/libelf/elf_getdata.c
@@ -1,6 +1,6 @@
 /* Return the next data element from the section after possibly converting it.
    Copyright (C) 1998-2005, 2006, 2007, 2015, 2016 Red Hat, Inc.
-   Copyright (C) 2022 Mark J. Wielaard <[email protected]>
+   Copyright (C) 2022, 2026 Mark J. Wielaard <[email protected]>
    This file is part of elfutils.
    Written by Ulrich Drepper <[email protected]>, 1998.
 
@@ -133,8 +133,9 @@ __libelf_data_type (GElf_Ehdr *ehdr, int sh_type, 
GElf_Xword align)
     }
 }
 
-/* Convert the data in the current section.  */
-static void
+/* Convert the data in the current section.  Returns 1 on failure
+   (setting elf errno to ELF_E_NOMEM), 0 on success.  */
+static int
 convert_data (Elf_Scn *scn, int eclass,
              int data, size_t size, Elf_Type type)
 {
@@ -152,7 +153,7 @@ convert_data (Elf_Scn *scn, int eclass,
          if (scn->data_base == NULL)
            {
              __libelf_seterrno (ELF_E_NOMEM);
-             return;
+             return 1;
            }
 
          /* The copy will be appropriately aligned for direct access.  */
@@ -167,7 +168,7 @@ convert_data (Elf_Scn *scn, int eclass,
       if (scn->data_base == NULL)
        {
          __libelf_seterrno (ELF_E_NOMEM);
-         return;
+         return 1;
        }
 
       /* Make sure the source is correctly aligned for the conversion
@@ -181,7 +182,7 @@ convert_data (Elf_Scn *scn, int eclass,
          if (rawdata_source == NULL)
            {
              __libelf_seterrno (ELF_E_NOMEM);
-             return;
+             return 1;
            }
 
          /* The copy will be appropriately aligned for direct access.  */
@@ -205,6 +206,8 @@ convert_data (Elf_Scn *scn, int eclass,
   scn->data_list.data.d.d_version = scn->rawdata.d.d_version;
 
   scn->data_list.data.s = scn;
+
+  return 0;
 }
 
 
@@ -433,7 +436,7 @@ __libelf_set_rawdata (Elf_Scn *scn)
   return result;
 }
 
-void
+int
 internal_function
 __libelf_set_data_list_rdlock (Elf_Scn *scn, int wrlocked)
 {
@@ -448,17 +451,18 @@ __libelf_set_data_list_rdlock (Elf_Scn *scn, int wrlocked)
          rwlock_unlock (elf->lock);
          rwlock_wrlock (elf->lock);
          if (scn->data_list_rear != NULL)
-           return;
+           return 0;
        }
 
       /* Convert according to the version and the type.   */
-      convert_data (scn, elf->class,
-                   (elf->class == ELFCLASS32
-                    || (offsetof (struct Elf, state.elf32.ehdr)
-                        == offsetof (struct Elf, state.elf64.ehdr))
-                    ? elf->state.elf32.ehdr->e_ident[EI_DATA]
-                    : elf->state.elf64.ehdr->e_ident[EI_DATA]),
-                   scn->rawdata.d.d_size, scn->rawdata.d.d_type);
+      if (convert_data (scn, elf->class,
+                       (elf->class == ELFCLASS32
+                        || (offsetof (struct Elf, state.elf32.ehdr)
+                            == offsetof (struct Elf, state.elf64.ehdr))
+                        ? elf->state.elf32.ehdr->e_ident[EI_DATA]
+                        : elf->state.elf64.ehdr->e_ident[EI_DATA]),
+                       scn->rawdata.d.d_size, scn->rawdata.d.d_type) != 0)
+       return 1;
     }
   else
     {
@@ -469,6 +473,8 @@ __libelf_set_data_list_rdlock (Elf_Scn *scn, int wrlocked)
     }
 
   scn->data_list_rear = &scn->data_list;
+
+  return 0;
 }
 
 static Elf_Data *
@@ -559,8 +565,9 @@ get_data_lock (Elf_Scn *scn, Elf_Data *data, int wrlocked)
   /* At this point we know the raw data is available.  But it might be
      empty in case the section has size zero (for whatever reason).
      Now create the converted data in case this is necessary.  */
-  if (scn->data_list_rear == NULL)
-    __libelf_set_data_list_rdlock (scn, wrlocked);
+  if (scn->data_list_rear == NULL
+      && __libelf_set_data_list_rdlock (scn, wrlocked) != 0)
+    goto out;
 
   /* Return the first data element in the list.  */
   result = &scn->data_list.data.d;
diff --git a/libelf/elf_newdata.c b/libelf/elf_newdata.c
index b59b87ecfc0a..7eadb9f05b2f 100644
--- a/libelf/elf_newdata.c
+++ b/libelf/elf_newdata.c
@@ -80,8 +80,9 @@ elf_newdata (Elf_Scn *scn)
      already data available.  */
   if (scn->data_read
       && scn->rawdata_base != NULL
-      && scn->data_list_rear == NULL)
-    __libelf_set_data_list_rdlock (scn, 1);
+      && scn->data_list_rear == NULL
+      && __libelf_set_data_list_rdlock (scn, 1) != 0)
+    goto out;
 
   if (scn->data_read && scn->data_list_rear == NULL)
     {
@@ -99,10 +100,10 @@ elf_newdata (Elf_Scn *scn)
         load/convert any data if data_list_rear is set.  */
       if (scn->data_read == 0)
        {
-         if (__libelf_set_rawdata_wrlock (scn) != 0)
+         if (__libelf_set_rawdata_wrlock (scn) != 0
+             || __libelf_set_data_list_rdlock (scn, 1) != 0)
            /* Something went wrong.  The error value is already set.  */
            goto out;
-         __libelf_set_data_list_rdlock (scn, 1);
        }
 
       /* Create a new, empty data descriptor.  */
diff --git a/libelf/libelfP.h b/libelf/libelfP.h
index dae775c6d423..f73f044dddec 100644
--- a/libelf/libelfP.h
+++ b/libelf/libelfP.h
@@ -1,6 +1,6 @@
 /* Internal interfaces for libelf.
    Copyright (C) 1998-2010, 2015, 2016 Red Hat, Inc.
-   Copyright (C) 2023 Mark J. Wielaard <[email protected]>
+   Copyright (C) 2023, 2026 Mark J. Wielaard <[email protected]>
    This file is part of elfutils.
    Contributed by Ulrich Drepper <[email protected]>, 1998.
 
@@ -550,8 +550,9 @@ extern Elf_Data *__elf_rawdata_internal (Elf_Scn *__scn, 
Elf_Data *__data)
 /* Should be called to setup first section data element if
    data_list_rear is NULL and we know data_read is set and there is
    raw data available.  Might upgrade the ELF lock from a read to a
-   write lock.  If the lock is already a write lock set wrlocked.  */
-extern void __libelf_set_data_list_rdlock (Elf_Scn *scn, int wrlocked)
+   write lock.  If the lock is already a write lock set wrlocked.
+   Returns 0 on success and -1 on failure (and sets libelf errno).  */
+extern int __libelf_set_data_list_rdlock (Elf_Scn *scn, int wrlocked)
      internal_function;
 extern char *__elf_strptr_internal (Elf *__elf, size_t __index,
                                    size_t __offset) attribute_hidden;
@@ -607,9 +608,11 @@ extern void * __libelf_decompress_elf_wrlock (Elf_Scn 
*scn, size_t *size_out,
                                              size_t *addralign)
      internal_function;
 
-
-extern void __libelf_reset_rawdata (Elf_Scn *scn, void *buf, size_t size,
-                                   size_t align, Elf_Type type)
+/* Resets the rawdata buffer of an Elf_Scn. Assumes buf is a malloced
+   buffer.  Returns 1 on failure (and sets libelf errno), 0 on
+   success.  */
+extern int __libelf_reset_rawdata (Elf_Scn *scn, void *buf, size_t size,
+                                  size_t align, Elf_Type type)
      internal_function;
 
 
-- 
2.56.0

Reply via email to