From c87ab64b8b6c39b2535147ff99e6044b4e649309 Mon Sep 17 00:00:00 2001
From: Karan Kurani <karankurani3k@gmail.com>
Date: Tue, 21 Jul 2026 10:08:24 +0000
Subject: [RFC PATCH 1/2] libelf: Check output layout arithmetic before
 serialization

elf_update computes the complete output layout in the shared ELF32/ELF64
layout builder before passing the result to either the mmap or pwrite
serializer.  Several section, program-header, and section-header extents
were added in the destination ELF field width, so caller-selected
ELF_F_LAYOUT values could wrap before the output size was allocated or
mapped.

Use a uint64_t layout accumulator and small private inline helpers for
checked addition, alignment, and extent accumulation.  Keep error handling
on the failure path and combine the extent limit checks so the common path
does not repeat work.  Reject wrapped extents and values larger than
INT64_MAX because elf_update returns int64_t.  Check values before narrowing
them to ELF32 offset and size fields.

Preserve the existing distinct d_align behavior: zero resets the calculated
descriptor offset to zero, while one leaves it unchanged.  Reject a negative
Elf_Data offset and all arithmetic range failures with ELF_E_RANGE.  Check the
automatic-layout running offset before storing it in the signed Elf_Data.d_off
field.

This protects the common layout boundary used by both serializers without
changing the public API or ABI.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34420
Signed-off-by: Karan Kurani <karankurani3k@gmail.com>
---
 libelf/elf32_updatenull.c | 147 +++++++++++++++++++++++++++++++-------
 1 file changed, 122 insertions(+), 25 deletions(-)

diff --git a/libelf/elf32_updatenull.c b/libelf/elf32_updatenull.c
index 57239ee4..6d3e20a3 100644
--- a/libelf/elf32_updatenull.c
+++ b/libelf/elf32_updatenull.c
@@ -34,6 +34,7 @@
 #include <assert.h>
 #include <libelf.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <string.h>
 
 #include "libelfP.h"
@@ -49,6 +50,58 @@
 #define Elf64_SizeWord Elf64_Xword
 
 
+/* The updatefile functions rely on the size computed here when allocating
+   or mapping the output file.  elf_update returns int64_t, so larger layouts
+   are unrepresentable.  */
+static inline bool
+layout_add (uint64_t left, uint64_t right, uint64_t *result)
+{
+  if (unlikely (right > UINT64_MAX - left))
+    {
+      __libelf_seterrno (ELF_E_RANGE);
+      return false;
+    }
+
+  *result = left + right;
+  return true;
+}
+
+static inline bool
+layout_align (uint64_t value, uint64_t align, uint64_t *result)
+{
+  if (align == 0)
+    {
+      *result = 0;
+      return true;
+    }
+
+  uint64_t padding = align - 1;
+  if (unlikely (padding > UINT64_MAX - value))
+    {
+      __libelf_seterrno (ELF_E_RANGE);
+      return false;
+    }
+
+  *result = (value + padding) & ~padding;
+
+  return true;
+}
+
+static inline bool
+layout_extent (uint64_t *size, uint64_t offset, uint64_t extent)
+{
+  if (unlikely (offset > INT64_MAX
+		|| extent > (uint64_t) INT64_MAX - offset))
+    {
+      __libelf_seterrno (ELF_E_RANGE);
+      return false;
+    }
+
+  uint64_t end = offset + extent;
+  *size = MAX (*size, end);
+  return true;
+}
+
 static int
 ELFW(default_ehdr,LIBELFBITS) (Elf *elf, ElfW2(LIBELFBITS,Ehdr) *ehdr,
 			       size_t shnum, int *change_bop)
@@ -140,7 +193,9 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
     return -1;
 
   /* At least the ELF header is there.  */
-  ElfW2(LIBELFBITS,SizeWord) size = elf_typesize (LIBELFBITS, ELF_T_EHDR, 1);
+  uint64_t size = elf_typesize (LIBELFBITS, ELF_T_EHDR, 1);
+  const uint64_t offset_max = (ElfW2(LIBELFBITS,Off)) -1;
+  const uint64_t size_max = (ElfW2(LIBELFBITS,SizeWord)) -1;
 
   /* Set the program header position.  */
   if (elf->state.ELFW(elf,LIBELFBITS).phdr == NULL)
@@ -151,13 +206,15 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
       if (unlikely (__elf_getphdrnum_rdlock (elf, &phnum) != 0))
 	return -1;
 
+      const uint64_t phdr_size
+	= elf_typesize (LIBELFBITS, ELF_T_PHDR, phnum);
+
       if (elf->flags & ELF_F_LAYOUT)
 	{
 	  /* The user is supposed to fill out e_phoff.  Use it and
 	     e_phnum to determine the maximum extend.  */
-	  size = MAX (size,
-		      ehdr->e_phoff
-		      + elf_typesize (LIBELFBITS, ELF_T_PHDR, phnum));
+	  if (! layout_extent (&size, ehdr->e_phoff, phdr_size))
+	    return -1;
 	}
       else
 	{
@@ -166,7 +223,8 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 			     ehdr_flags);
 
 	  /* We need no alignment here.  */
-	  size += elf_typesize (LIBELFBITS, ELF_T_PHDR, phnum);
+	  if (! layout_extent (&size, size, phdr_size))
+	    return -1;
 	}
     }
 
@@ -212,7 +270,7 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 	    {
 	      Elf_Scn *scn = &list->data[cnt];
 	      ElfW2(LIBELFBITS,Shdr) *shdr = scn->shdr.ELFW(e,LIBELFBITS);
-	      int64_t offset = 0;
+	      uint64_t offset = 0;
 
 	      assert (shdr != NULL);
 	      ElfW2(LIBELFBITS,SizeWord) sh_entsize = shdr->sh_entsize;
@@ -321,9 +379,16 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 			  /* The user specified the offset and the size.
 			     All we have to do is check whether this block
 			     fits in the size specified for the section.  */
-			  if (unlikely ((ElfW2(LIBELFBITS,SizeWord))
-					(data->d_off + data->d_size)
-					> shdr->sh_size))
+			  uint64_t data_end;
+			  if (unlikely (data->d_off < 0))
+			    {
+			      __libelf_seterrno (ELF_E_RANGE);
+			      return -1;
+			    }
+			  if (! layout_add (data->d_off, data->d_size,
+					    &data_end))
+			    return -1;
+			  if (unlikely (data_end > shdr->sh_size))
 			    {
 			      __libelf_seterrno (ELF_E_SECTION_TOO_SMALL);
 			      return -1;
@@ -332,12 +397,20 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 		      else
 			{
 			  /* Determine the padding.  */
-			  offset = ((offset + data->d_align - 1)
-				    & ~(data->d_align - 1));
+			  if (! layout_align (offset, data->d_align, &offset))
+			    return -1;
+
+			  if (unlikely (offset > INT64_MAX))
+			    {
+			      __libelf_seterrno (ELF_E_RANGE);
+			      return -1;
+			    }
 
-			  update_if_changed (data->d_off, offset, changed);
+			  update_if_changed (data->d_off, (int64_t) offset,
+					     changed);
 
-			  offset += data->d_size;
+			  if (! layout_add (offset, data->d_size, &offset))
+			    return -1;
 			}
 
 		      /* Next data block.  */
@@ -347,13 +420,15 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 	      else
 		/* Get the size of the section from the raw data.  If
 		   none is available the value is zero.  */
-		offset += scn->rawdata.d.d_size;
+		if (! layout_add (offset, scn->rawdata.d.d_size, &offset))
+		  return -1;
 
 	      if (elf->flags & ELF_F_LAYOUT)
 		{
-		  size = MAX (size,
-			      (shdr->sh_type != SHT_NOBITS
-			       ? shdr->sh_offset + shdr->sh_size : 0));
+		  if (shdr->sh_type != SHT_NOBITS
+		      && ! layout_extent (&size, shdr->sh_offset,
+					  shdr->sh_size))
+		    return -1;
 
 		  /* The alignment must be a power of two.  This is a
 		     requirement from the ELF specification.  Additionally
@@ -373,7 +448,13 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 		  update_if_changed (shdr->sh_addralign, sh_align,
 				     scn->shdr_flags);
 
-		  size = (size + sh_align - 1) & ~(sh_align - 1);
+		  if (! layout_align (size, sh_align, &size))
+		    return -1;
+		  if (unlikely (size > offset_max))
+		    {
+		      __libelf_seterrno (ELF_E_RANGE);
+		      return -1;
+		    }
 		  int offset_changed = 0;
 		  update_if_changed (shdr->sh_offset, size, offset_changed);
 		  changed |= offset_changed;
@@ -387,14 +468,20 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 		    }
 
 		  /* See whether the section size is correct.  */
+		  if (unlikely (offset > size_max))
+		    {
+		      __libelf_seterrno (ELF_E_RANGE);
+		      return -1;
+		    }
 		  int size_changed = 0;
 		  update_if_changed (shdr->sh_size,
 				     (ElfW2(LIBELFBITS,SizeWord)) offset,
 				     size_changed);
 		  changed |= size_changed;
 
-		  if (shdr->sh_type != SHT_NOBITS)
-		    size += offset;
+		  if (shdr->sh_type != SHT_NOBITS
+		      && ! layout_extent (&size, size, offset))
+		    return -1;
 
 		  scn->shdr_flags |= (offset_changed | size_changed);
 		  scn->flags |= changed;
@@ -440,9 +527,10 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 	  /* The user is supposed to fill out e_shoff.  Use it and
 	     e_shnum (or sh_size of the dummy, first section header)
 	     to determine the maximum extend.  */
-	  size = MAX (size,
-		      (ehdr->e_shoff
-		       + (elf_typesize (LIBELFBITS, ELF_T_SHDR, shnum))));
+	  const uint64_t shdr_size
+	    = elf_typesize (LIBELFBITS, ELF_T_SHDR, shnum);
+	  if (! layout_extent (&size, ehdr->e_shoff, shdr_size))
+	    return -1;
 	}
       else
 	{
@@ -452,12 +540,21 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
 	     want to be surprised by architectures with less strict
 	     alignment rules.  */
 #define SHDR_ALIGN sizeof (ElfW2(LIBELFBITS,Off))
-	  size = (size + SHDR_ALIGN - 1) & ~(SHDR_ALIGN - 1);
+	  if (! layout_align (size, SHDR_ALIGN, &size))
+	    return -1;
+	  if (unlikely (size > offset_max))
+	    {
+	      __libelf_seterrno (ELF_E_RANGE);
+	      return -1;
+	    }
 
 	  update_if_changed (ehdr->e_shoff, size, elf->flags);
 
 	  /* Account for the section header size.  */
-	  size += elf_typesize (LIBELFBITS, ELF_T_SHDR, shnum);
+	  const uint64_t shdr_size
+	    = elf_typesize (LIBELFBITS, ELF_T_SHDR, shnum);
+	  if (! layout_extent (&size, size, shdr_size))
+	    return -1;
 	}
     }
 
-- 
2.44.0

