From dac4d132724c2daf70f743ab1281c8eabc6eca12 Mon Sep 17 00:00:00 2001 From: sstamenk Date: Mon, 31 Aug 2026 18:01:11 +0200 Subject: [PATCH 1/3] Backport ELFIO note bounds validation Add the ELFIO 3.12 backport for bounded note parsing while the upstream fix is reviewed in serge1/ELFIO#173. Refs: SWSPLAT-24318 --- .../0002-Validate-note-accessor-bounds.patch | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch diff --git a/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch b/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch new file mode 100644 index 00000000000..848117e77b2 --- /dev/null +++ b/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch @@ -0,0 +1,151 @@ +From d66c8453c6239320efc39ad462cd0816685ab1f0 Mon Sep 17 00:00:00 2001 +From: sstamenk +Date: Mon, 31 Aug 2026 14:56:31 +0200 +Subject: [PATCH] Validate note accessor bounds + +ELFIO 3.12 compares a requested note index with the section size in bytes before indexing the parsed note-position vector. A caller that probes an out-of-range index can therefore read beyond the vector. Truncated name or descriptor payloads also need complete padded-extent validation before data is returned. + +Use bounded, alignment-safe header reads and validate parsed note extents. + +This is a temporary TheRock backport while the fix is reviewed upstream. + +Upstream tracking: https://github.com/serge1/ELFIO/pull/173 + +Reproduction: create a section with one note and call get_note(1); ASan reports a heap-buffer-overflow in note_start_positions[index]. +--- + elfio/elfio_note.hpp | 93 ++++++++++++++++++++++++++++++-------------- + 1 file changed, 63 insertions(+), 30 deletions(-) + +diff --git a/elfio/elfio_note.hpp b/elfio/elfio_note.hpp +index 63ef102..5ecbc40 100644 +--- a/elfio/elfio_note.hpp ++++ b/elfio/elfio_note.hpp +@@ -23,6 +23,8 @@ THE SOFTWARE. + #ifndef ELFIO_NOTE_HPP + #define ELFIO_NOTE_HPP + ++#include ++ + namespace ELFIO { + + //------------------------------------------------------------------------------ +@@ -62,32 +64,56 @@ class note_section_accessor_template + char*& desc, + Elf_Word& descSize ) const + { +- if ( index >= ( notes->*F_get_size )() ) { ++ if ( index >= note_start_positions.size() ) { + return false; + } + +- const char* pData = notes->get_data() + note_start_positions[index]; +- int align = sizeof( Elf_Word ); ++ const char* data = notes->get_data(); ++ Elf_Xword data_size = ( notes->*F_get_size )(); ++ Elf_Xword position = note_start_positions[index]; ++ Elf_Xword align = sizeof( Elf_Word ); ++ Elf_Xword header_size = 3 * align; ++ if ( data == nullptr || position > data_size || ++ header_size > data_size - position ) { ++ return false; ++ } + + const endianess_convertor& convertor = elf_file.get_convertor(); +- type = convertor( *(const Elf_Word*)( pData + 2 * (size_t)align ) ); +- Elf_Word namesz = convertor( *(const Elf_Word*)( pData ) ); +- descSize = convertor( *(const Elf_Word*)( pData + sizeof( namesz ) ) ); +- +- Elf_Xword max_name_size = +- ( notes->*F_get_size )() - note_start_positions[index]; +- if ( namesz < 1 || namesz > max_name_size || +- (Elf_Xword)namesz + descSize > max_name_size ) { ++ const char* pData = data + position; ++ Elf_Word raw_namesz; ++ Elf_Word raw_descsz; ++ Elf_Word raw_type; ++ std::memcpy( &raw_namesz, pData, sizeof( raw_namesz ) ); ++ std::memcpy( &raw_descsz, pData + align, sizeof( raw_descsz ) ); ++ std::memcpy( &raw_type, pData + 2 * align, sizeof( raw_type ) ); ++ ++ Elf_Word namesz = convertor( raw_namesz ); ++ Elf_Word parsed_descsz = convertor( raw_descsz ); ++ Elf_Word parsed_type = convertor( raw_type ); ++ Elf_Xword padded_name = ++ ( static_cast( namesz ) + align - 1 ) / align * align; ++ Elf_Xword padded_desc = ++ ( static_cast( parsed_descsz ) + align - 1 ) / align * ++ align; ++ Elf_Xword remaining = data_size - position - header_size; ++ if ( padded_name > remaining || ++ padded_desc > remaining - padded_name ) { + return false; + } +- name.assign( pData + 3 * (size_t)align, namesz - 1 ); +- if ( 0 == descSize ) { ++ ++ type = parsed_type; ++ descSize = parsed_descsz; ++ if ( namesz == 0 ) { ++ name.clear(); ++ } ++ else { ++ name.assign( pData + header_size, namesz - 1 ); ++ } ++ if ( 0 == parsed_descsz ) { + desc = nullptr; + } + else { +- desc = const_cast( pData + 3 * (size_t)align + +- ( ( namesz + align - 1 ) / align ) * +- (size_t)align ); ++ desc = const_cast( pData + header_size + padded_name ); + } + + return true; +@@ -143,23 +169,30 @@ class note_section_accessor_template + return; + } + +- Elf_Word align = sizeof( Elf_Word ); +- while ( current + (Elf_Xword)3 * align <= size ) { +- Elf_Word namesz = convertor( *(const Elf_Word*)( data + current ) ); +- Elf_Word descsz = convertor( +- *(const Elf_Word*)( data + current + sizeof( namesz ) ) ); +- Elf_Word advance = +- (Elf_Xword)3 * sizeof( Elf_Word ) + +- ( ( namesz + align - 1 ) / align ) * (Elf_Xword)align + +- ( ( descsz + align - 1 ) / align ) * (Elf_Xword)align; +- if ( namesz < size && descsz < size && current + advance <= size ) { +- note_start_positions.emplace_back( current ); +- } +- else { ++ const Elf_Xword align = sizeof( Elf_Word ); ++ const Elf_Xword header_size = 3 * align; ++ while ( current <= size && header_size <= size - current ) { ++ Elf_Word raw_namesz; ++ Elf_Word raw_descsz; ++ std::memcpy( &raw_namesz, data + current, sizeof( raw_namesz ) ); ++ std::memcpy( &raw_descsz, data + current + sizeof( raw_namesz ), ++ sizeof( raw_descsz ) ); ++ Elf_Word namesz = convertor( raw_namesz ); ++ Elf_Word descsz = convertor( raw_descsz ); ++ Elf_Xword padded_name = ++ ( static_cast( namesz ) + align - 1 ) / align * ++ align; ++ Elf_Xword padded_desc = ++ ( static_cast( descsz ) + align - 1 ) / align * ++ align; ++ Elf_Xword remaining = size - current - header_size; ++ if ( padded_name > remaining || ++ padded_desc > remaining - padded_name ) { + break; + } + +- current += advance; ++ note_start_positions.emplace_back( current ); ++ current += header_size + padded_name + padded_desc; + } + } + +-- +2.43.0 From 04dcfc4202c9e9bf737ea46e8a40c79a922c2f2a Mon Sep 17 00:00:00 2001 From: sstamenk Date: Thu, 17 Sep 2026 19:13:46 +0200 Subject: [PATCH 2/3] Refresh ELFIO note parsing backport Synchronize the ELFIO 3.12 note accessor updates with upstream and simplify patch notes. Refs: SWSPLAT-24318 --- .../0002-Validate-note-accessor-bounds.patch | 210 ++++++++++-------- 1 file changed, 116 insertions(+), 94 deletions(-) diff --git a/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch b/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch index 848117e77b2..03c12df8122 100644 --- a/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch +++ b/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch @@ -1,35 +1,28 @@ -From d66c8453c6239320efc39ad462cd0816685ab1f0 Mon Sep 17 00:00:00 2001 -From: sstamenk -Date: Mon, 31 Aug 2026 14:56:31 +0200 -Subject: [PATCH] Validate note accessor bounds +Subject: [PATCH] Update ELFIO note parsing -ELFIO 3.12 compares a requested note index with the section size in bytes before indexing the parsed note-position vector. A caller that probes an out-of-range index can therefore read beyond the vector. Truncated name or descriptor payloads also need complete padded-extent validation before data is returned. +Backport the upstream note accessor updates to ELFIO 3.12. Share record +decoding between scanning and lookup, preserve nameless notes, and keep +output parameters unchanged when a record cannot be read. -Use bounded, alignment-safe header reads and validate parsed note extents. +Upstream: https://github.com/serge1/ELFIO/pull/173 +JIRA: SWSPLAT-24318 -This is a temporary TheRock backport while the fix is reviewed upstream. - -Upstream tracking: https://github.com/serge1/ELFIO/pull/173 - -Reproduction: create a section with one note and call get_note(1); ASan reports a heap-buffer-overflow in note_start_positions[index]. ---- - elfio/elfio_note.hpp | 93 ++++++++++++++++++++++++++++++-------------- - 1 file changed, 63 insertions(+), 30 deletions(-) +Remove this backport when updating to a release that includes these changes. diff --git a/elfio/elfio_note.hpp b/elfio/elfio_note.hpp -index 63ef102..5ecbc40 100644 +index 63ef102..bd5189d 100644 --- a/elfio/elfio_note.hpp +++ b/elfio/elfio_note.hpp @@ -23,6 +23,8 @@ THE SOFTWARE. #ifndef ELFIO_NOTE_HPP #define ELFIO_NOTE_HPP - + +#include + namespace ELFIO { - + //------------------------------------------------------------------------------ -@@ -62,32 +64,56 @@ class note_section_accessor_template +@@ -62,34 +64,23 @@ class note_section_accessor_template char*& desc, Elf_Word& descSize ) const { @@ -37,20 +30,11 @@ index 63ef102..5ecbc40 100644 + if ( index >= note_start_positions.size() ) { return false; } - + - const char* pData = notes->get_data() + note_start_positions[index]; - int align = sizeof( Elf_Word ); -+ const char* data = notes->get_data(); -+ Elf_Xword data_size = ( notes->*F_get_size )(); -+ Elf_Xword position = note_start_positions[index]; -+ Elf_Xword align = sizeof( Elf_Word ); -+ Elf_Xword header_size = 3 * align; -+ if ( data == nullptr || position > data_size || -+ header_size > data_size - position ) { -+ return false; -+ } - - const endianess_convertor& convertor = elf_file.get_convertor(); +- +- const endianess_convertor& convertor = elf_file.get_convertor(); - type = convertor( *(const Elf_Word*)( pData + 2 * (size_t)align ) ); - Elf_Word namesz = convertor( *(const Elf_Word*)( pData ) ); - descSize = convertor( *(const Elf_Word*)( pData + sizeof( namesz ) ) ); @@ -59,53 +43,87 @@ index 63ef102..5ecbc40 100644 - ( notes->*F_get_size )() - note_start_positions[index]; - if ( namesz < 1 || namesz > max_name_size || - (Elf_Xword)namesz + descSize > max_name_size ) { -+ const char* pData = data + position; -+ Elf_Word raw_namesz; -+ Elf_Word raw_descsz; -+ Elf_Word raw_type; -+ std::memcpy( &raw_namesz, pData, sizeof( raw_namesz ) ); -+ std::memcpy( &raw_descsz, pData + align, sizeof( raw_descsz ) ); -+ std::memcpy( &raw_type, pData + 2 * align, sizeof( raw_type ) ); -+ -+ Elf_Word namesz = convertor( raw_namesz ); -+ Elf_Word parsed_descsz = convertor( raw_descsz ); -+ Elf_Word parsed_type = convertor( raw_type ); -+ Elf_Xword padded_name = -+ ( static_cast( namesz ) + align - 1 ) / align * align; -+ Elf_Xword padded_desc = -+ ( static_cast( parsed_descsz ) + align - 1 ) / align * -+ align; -+ Elf_Xword remaining = data_size - position - header_size; -+ if ( padded_name > remaining || -+ padded_desc > remaining - padded_name ) { ++ const char* data = notes->get_data(); ++ const Elf_Xword data_size = ( notes->*F_get_size )(); ++ note_record record; ++ // The section may have changed since its note offsets were collected. ++ if ( !read_note( data, data_size, note_start_positions[index], ++ record ) ) { return false; } - name.assign( pData + 3 * (size_t)align, namesz - 1 ); - if ( 0 == descSize ) { -+ -+ type = parsed_type; -+ descSize = parsed_descsz; -+ if ( namesz == 0 ) { -+ name.clear(); -+ } -+ else { -+ name.assign( pData + header_size, namesz - 1 ); -+ } -+ if ( 0 == parsed_descsz ) { - desc = nullptr; - } - else { +- desc = nullptr; +- } +- else { - desc = const_cast( pData + 3 * (size_t)align + - ( ( namesz + align - 1 ) / align ) * - (size_t)align ); -+ desc = const_cast( pData + header_size + padded_name ); - } - +- } + ++ name.assign( record.name, record.name_size ); ++ type = record.type; ++ descSize = record.descriptor_size; ++ desc = const_cast( record.descriptor ); return true; -@@ -143,23 +169,30 @@ class note_section_accessor_template - return; + } + +@@ -128,38 +119,75 @@ class note_section_accessor_template + } + + private: +- //------------------------------------------------------------------------------ +- void process_section() ++ struct note_record + { +- const endianess_convertor& convertor = elf_file.get_convertor(); +- const char* data = notes->get_data(); +- Elf_Xword size = ( notes->*F_get_size )(); +- Elf_Xword current = 0; ++ Elf_Word type; ++ const char* name; ++ Elf_Word name_size; ++ const char* descriptor; ++ Elf_Word descriptor_size; ++ Elf_Xword total_size; ++ }; ++ ++ static Elf_Xword padded_size( Elf_Word size ) ++ { ++ constexpr Elf_Xword alignment = sizeof( Elf_Word ); ++ return ( static_cast( size ) + alignment - 1 ) / alignment * ++ alignment; ++ } + +- note_start_positions.clear(); ++ // Decode one complete record. Scanning and lookup share these checks. ++ bool read_note( const char* data, ++ Elf_Xword data_size, ++ Elf_Xword position, ++ note_record& record ) const ++ { ++ constexpr Elf_Xword header_size = 3 * sizeof( Elf_Word ); ++ if ( data == nullptr || position > data_size || ++ header_size > data_size - position ) { ++ return false; ++ } + +- // Is it empty? +- if ( nullptr == data || 0 == size ) { +- return; ++ Elf_Word header[3]; ++ std::memcpy( header, data + position, sizeof( header ) ); ++ const auto& convertor = elf_file.get_convertor(); ++ const Elf_Word namesz = convertor( header[0] ); ++ const Elf_Word descsz = convertor( header[1] ); ++ const Elf_Xword padded_name = padded_size( namesz ); ++ const Elf_Xword padded_desc = padded_size( descsz ); ++ const Elf_Xword remaining = data_size - position - header_size; ++ if ( padded_name > remaining || ++ padded_desc > remaining - padded_name ) { ++ return false; } - + - Elf_Word align = sizeof( Elf_Word ); - while ( current + (Elf_Xword)3 * align <= size ) { - Elf_Word namesz = convertor( *(const Elf_Word*)( data + current ) ); @@ -119,33 +137,37 @@ index 63ef102..5ecbc40 100644 - note_start_positions.emplace_back( current ); - } - else { -+ const Elf_Xword align = sizeof( Elf_Word ); -+ const Elf_Xword header_size = 3 * align; -+ while ( current <= size && header_size <= size - current ) { -+ Elf_Word raw_namesz; -+ Elf_Word raw_descsz; -+ std::memcpy( &raw_namesz, data + current, sizeof( raw_namesz ) ); -+ std::memcpy( &raw_descsz, data + current + sizeof( raw_namesz ), -+ sizeof( raw_descsz ) ); -+ Elf_Word namesz = convertor( raw_namesz ); -+ Elf_Word descsz = convertor( raw_descsz ); -+ Elf_Xword padded_name = -+ ( static_cast( namesz ) + align - 1 ) / align * -+ align; -+ Elf_Xword padded_desc = -+ ( static_cast( descsz ) + align - 1 ) / align * -+ align; -+ Elf_Xword remaining = size - current - header_size; -+ if ( padded_name > remaining || -+ padded_desc > remaining - padded_name ) { - break; - } - +- break; +- } ++ const char* name = data + position + header_size; ++ if ( namesz != 0 && name[namesz - 1] != '\0' ) { ++ return false; ++ } ++ record.type = convertor( header[2] ); ++ record.name = name; ++ record.name_size = namesz == 0 ? 0 : namesz - 1; ++ record.descriptor = descsz == 0 ? nullptr : name + padded_name; ++ record.descriptor_size = descsz; ++ record.total_size = header_size + padded_name + padded_desc; ++ return true; ++ } + - current += advance; ++ //------------------------------------------------------------------------------ ++ void process_section() ++ { ++ note_start_positions.clear(); ++ if ( notes == nullptr ) { ++ return; ++ } ++ const char* data = notes->get_data(); ++ const Elf_Xword data_size = ( notes->*F_get_size )(); ++ Elf_Xword current = 0; ++ note_record record; ++ while ( read_note( data, data_size, current, record ) ) { + note_start_positions.emplace_back( current ); -+ current += header_size + padded_name + padded_desc; ++ current += record.total_size; } } - --- -2.43.0 + +-- From 66a6f7e43cab6e61085ed5b8c84ed72e9a78d145 Mon Sep 17 00:00:00 2001 From: sstamenk Date: Tue, 13 Oct 2026 17:35:04 +0200 Subject: [PATCH 3/3] Update ELFIO note parsing backport to upstream PR head Rebase the backport onto serge1/ELFIO#173 at 5aaee4f. Adds acceptance of owner names without a terminator, a zero descriptor size for notes added without data, 4-byte alignment of notes added after unaligned data, and a rescan in add_note() so cached indices stay consistent with a fresh accessor. Co-Authored-By: Claude Opus 5.5 --- .../0002-Validate-note-accessor-bounds.patch | 87 ++++++++++++++----- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch b/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch index 03c12df8122..33a56382a2f 100644 --- a/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch +++ b/patches/third-party/elfio/0002-Validate-note-accessor-bounds.patch @@ -1,8 +1,10 @@ Subject: [PATCH] Update ELFIO note parsing Backport the upstream note accessor updates to ELFIO 3.12. Share record -decoding between scanning and lookup, preserve nameless notes, and keep -output parameters unchanged when a record cannot be read. +decoding between scanning and lookup, preserve nameless notes and notes +whose owner name lacks a terminator, keep output parameters unchanged when +a record cannot be read, and keep notes added with add_note() aligned and +consistently indexed. Upstream: https://github.com/serge1/ELFIO/pull/173 JIRA: SWSPLAT-24318 @@ -10,17 +12,17 @@ JIRA: SWSPLAT-24318 Remove this backport when updating to a release that includes these changes. diff --git a/elfio/elfio_note.hpp b/elfio/elfio_note.hpp -index 63ef102..bd5189d 100644 +index 63ef102..6c1adc4 100644 --- a/elfio/elfio_note.hpp +++ b/elfio/elfio_note.hpp @@ -23,6 +23,8 @@ THE SOFTWARE. #ifndef ELFIO_NOTE_HPP #define ELFIO_NOTE_HPP - + +#include + namespace ELFIO { - + //------------------------------------------------------------------------------ @@ -62,34 +64,23 @@ class note_section_accessor_template char*& desc, @@ -30,7 +32,7 @@ index 63ef102..bd5189d 100644 + if ( index >= note_start_positions.size() ) { return false; } - + - const char* pData = notes->get_data() + note_start_positions[index]; - int align = sizeof( Elf_Word ); - @@ -60,17 +62,53 @@ index 63ef102..bd5189d 100644 - ( ( namesz + align - 1 ) / align ) * - (size_t)align ); - } - + + name.assign( record.name, record.name_size ); + type = record.type; + descSize = record.descriptor_size; + desc = const_cast( record.descriptor ); return true; } - -@@ -128,38 +119,75 @@ class note_section_accessor_template + +@@ -101,6 +92,11 @@ class note_section_accessor_template + { + const endianess_convertor& convertor = elf_file.get_convertor(); + ++ // A record without descriptor data must not claim a descriptor ++ if ( desc == nullptr ) { ++ descSize = 0; ++ } ++ + int align = sizeof( Elf_Word ); + Elf_Word nameLen = (Elf_Word)name.size() + 1; + Elf_Word nameLenConv = convertor( nameLen ); +@@ -123,44 +119,99 @@ class note_section_accessor_template + } + } + +- note_start_positions.emplace_back( ( notes->*F_get_size )() ); +- notes->append_data( buffer ); ++ // Records start on a 4-byte boundary, even when the existing data ++ // does not end on one ++ constexpr Elf_Xword word_size = sizeof( Elf_Word ); ++ const Elf_Xword current_size = ( notes->*F_get_size )(); ++ const Elf_Xword padding = ++ ( word_size - current_size % word_size ) % word_size; ++ notes->append_data( ++ std::string( static_cast( padding ), '\0' ) + buffer ); ++ if ( current_size == scanned_size ) { ++ // The existing data decoded completely, so the new record simply ++ // follows it ++ note_start_positions.emplace_back( current_size ); ++ scanned_size = current_size + buffer.size(); ++ } ++ else { ++ // The appended bytes may complete a record the scan stopped at, so ++ // rescan to keep indices consistent with a fresh accessor ++ process_section(); ++ } } - + private: - //------------------------------------------------------------------------------ - void process_section() @@ -94,7 +132,7 @@ index 63ef102..bd5189d 100644 + return ( static_cast( size ) + alignment - 1 ) / alignment * + alignment; + } - + - note_start_positions.clear(); + // Decode one complete record. Scanning and lookup share these checks. + bool read_note( const char* data, @@ -107,7 +145,7 @@ index 63ef102..bd5189d 100644 + header_size > data_size - position ) { + return false; + } - + - // Is it empty? - if ( nullptr == data || 0 == size ) { - return; @@ -123,7 +161,7 @@ index 63ef102..bd5189d 100644 + padded_desc > remaining - padded_name ) { + return false; } - + - Elf_Word align = sizeof( Elf_Word ); - while ( current + (Elf_Xword)3 * align <= size ) { - Elf_Word namesz = convertor( *(const Elf_Word*)( data + current ) ); @@ -140,23 +178,23 @@ index 63ef102..bd5189d 100644 - break; - } + const char* name = data + position + header_size; -+ if ( namesz != 0 && name[namesz - 1] != '\0' ) { -+ return false; -+ } ++ // Some producers omit the owner's terminator; keep the full name then ++ const bool terminated = namesz != 0 && name[namesz - 1] == '\0'; + record.type = convertor( header[2] ); + record.name = name; -+ record.name_size = namesz == 0 ? 0 : namesz - 1; ++ record.name_size = terminated ? namesz - 1 : namesz; + record.descriptor = descsz == 0 ? nullptr : name + padded_name; + record.descriptor_size = descsz; + record.total_size = header_size + padded_name + padded_desc; + return true; + } - + - current += advance; + //------------------------------------------------------------------------------ + void process_section() + { + note_start_positions.clear(); ++ scanned_size = 0; + if ( notes == nullptr ) { + return; + } @@ -168,6 +206,15 @@ index 63ef102..bd5189d 100644 + note_start_positions.emplace_back( current ); + current += record.total_size; } ++ scanned_size = current; } - --- + + //------------------------------------------------------------------------------ +@@ -168,6 +219,7 @@ class note_section_accessor_template + const elfio& elf_file; + S* notes; + std::vector note_start_positions; ++ Elf_Xword scanned_size = 0; // End of the records decoded by the last scan + }; + + using note_section_accessor =