eliben / pyelftools

Parsing ELF and DWARF in Python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Crash when iterating over notes by iterating over sections when a section is 8-byte aligned and padded

haadr opened this issue · comments

All below is tested with pyelftools Version: 0.30.

Context

As in #534, I've got a libqtwebenginequickplugin.so where qt has injected a .note.qt.metadata section and note.

# readelf --sections libqtwebenginequickplugin.so
There are 31 section headers, starting at offset 0x41f8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.pr[...] NOTE             00000000000002a8  000002a8
       0000000000000030  0000000000000000   A       0     0     8
  [ 2] .note.qt.metadata NOTE             00000000000002d8  000002d8
       0000000000000078  0000000000000000   A       0     0     8
...

As we can see, the note is of size 120.

Bug ❓

When trying to iterate over the notes I get

Traceback (most recent call last):
  File "pyelftools/elftools/construct/core.py", line 573, in _parse
    subobj = self.subcon._parse(stream, context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "pyelftools/elftools/construct/core.py", line 316, in _parse
    return _read_stream(stream, self.length)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "pyelftools/elftools/construct/core.py", line 293, in _read_stream
    raise FieldError("expected %d, found %d" % (length, len(data)))
elftools.construct.core.FieldError: expected 1, found 0

Using pyelftools to inspect the note before the crash, I see

Note section ".note.qt.metadata" at offset 728 with size 120
    Name: qt-project!
    Type: 1951465473
    Namesz: 12
    Descsz: 89
    Desc (89): 01060601bf02782c6f72672e71742d70726f6a6563742e51742e51516d6c457874656e73696f6e496e746572666163652f312e3003715174576562456e67696e65506c7567696e63757269816b5174576562456e67696e65ff

It looks like the section (and desc?) is padded with a total of 7 bytes.

hexdump -C .note.qt.metadata 
00000000  0c 00 00 00 59 00 00 00  01 00 51 74 71 74 2d 70  |....Y.....Qtqt-p|
00000010  72 6f 6a 65 63 74 21 00  01 06 06 01 bf 02 78 2c  |roject!.......x,|
00000020  6f 72 67 2e 71 74 2d 70  72 6f 6a 65 63 74 2e 51  |org.qt-project.Q|
00000030  74 2e 51 51 6d 6c 45 78  74 65 6e 73 69 6f 6e 49  |t.QQmlExtensionI|
00000040  6e 74 65 72 66 61 63 65  2f 31 2e 30 03 71 51 74  |nterface/1.0.qQt|
00000050  57 65 62 45 6e 67 69 6e  65 50 6c 75 67 69 6e 63  |WebEnginePluginc|
00000060  75 72 69 81 6b 51 74 57  65 62 45 6e 67 69 6e 65  |uri.kQtWebEngine|
00000070  ff 00 00 00 00 00 00 00                           |........|
00000078

Fix ❓

It seems to me that

Since this section is 8-byte aligned, the last loop will try to parse 4 bytes only (the rest of the padding), which is not enough for a complete note.

The following is a quick fix that worked for me, but I don't know whether it makes sense/is the best way to solve that. I reasoned that with an empty name and desc, 12 bytes is the smallest a note could be.

diff --git a/elftools/elf/notes.py b/elftools/elf/notes.py
index c708cbf..bf2895b 100644
--- a/elftools/elf/notes.py
+++ b/elftools/elf/notes.py
@@ -15,6 +15,9 @@ def iter_notes(elffile, offset, size):
     """
     end = offset + size
     while offset < end:
+        if end - offset < 12:
+            break
+
         note = struct_parse(
             elffile.structs.Elf_Nhdr,
             elffile.stream,

Reproduction

Script used to reproduce error (almost same as elf_notes.py example)

from __future__ import print_function
import sys

# If pyelftools is not installed, the example can also run from the root or
# examples/ dir of the source distribution.
sys.path[0:0] = ['.', '..']

from elftools.elf.elffile import ELFFile
from elftools.elf.sections import NoteSection
from elftools.common.utils import bytes2hex


def process_file(filename):
    print('Processing file:', filename)
    with open(filename, 'rb') as f:
        for sect in ELFFile(f).iter_sections():
            if not isinstance(sect, NoteSection):
                continue
            print('  Note section "%s" at offset 0x%.8x with size %d' % (
                sect.name, sect.header['sh_offset'], sect.header['sh_size']))
            for note in sect.iter_notes():
                print('    Name:', note['n_name'])
                print('    Type:', note['n_type'])
                desc = note['n_desc']
                if note['n_type'] == 'NT_GNU_ABI_TAG':
                    print('    Desc: %s, ABI: %d.%d.%d' % (
                        desc['abi_os'],
                        desc['abi_major'],
                        desc['abi_minor'],
                        desc['abi_tiny']))
                elif note['n_type'] in {'NT_GNU_BUILD_ID', 'NT_GNU_GOLD_VERSION', 'NT_GNU_PROPERTY_TYPE_0'}:
                    print('    Desc:', desc)
                else:
                    print('    Desc:', "".join(map(chr, desc)))


if __name__ == '__main__':
    if sys.argv[1] == '--test':
        for filename in sys.argv[2:]:
            process_file(filename)

output from readelf -a is attached.
shared object file is attached. it's libqtwebenginequickplugin.so from a standard openSUSE Tumbleweed install.

libqtwebenginequickplugin.so.txt
readelf-a.txt

for each note, the offset is incremented with an assumed 4-byte alignment (?)

The padding for note data should definitely always be 4-bytes. See "Note Section" chapter of the ELF spec.

Since this section is 8-byte aligned, the last loop will try to parse 4 bytes only (the rest of the padding), which is not enough for a complete note.

Ah I see, the alignment on the segment is 8 bytes, causing 4 additional 0x00 bytes after the 4 note padding bytes.

What I am not sure about is whether the p_memsz size is supposed to include or exclude padding. In the .elf you provided, the additional padding is included in the size.

That said, I feel like it's probably a good idea to stop trying to parse notes if there are not enough bytes left.

I'll work on a PR to fix this in the next days.

I've put up a fix PR for this bug here: #537

@eliben could you take a look? tyvm!