Add support for .note.gnu.property notes section (#386)
authorMarco Bonelli <marco@mebeim.net>
Tue, 7 Dec 2021 14:08:54 +0000 (15:08 +0100)
committerGitHub <noreply@github.com>
Tue, 7 Dec 2021 14:08:54 +0000 (06:08 -0800)
* Add support for .note.gnu.properties notes section

References:

- Doc: https://github.com/hjl-tools/linux-abi/wiki/linux-abi-draft.pdf
- Linux: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=00e19ceec80b03a43f626f891fcc53e57919f1b3
- Glibc: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86/dl-prop.h;h=385548fad3e4ad71dbdcdbfada58585c2f24ea5e;hb=HEAD
- Binutils: https://sourceware.org/git/?p=binutils-gdb.git&a=search&h=HEAD&st=commit&s=NT_GNU_PROPERTY_TYPE_0

* Add descriptions for .note.gnu.properties notes

* descriptions: add missing PT_GNU_PROPERTY description

* py3compat: add optional separator for bytes2hex

* readelf: align notes column headers

* elf/descriptions: conform to real readelf's output format

* test: special case some known readelf output quirks

* test: add test ELFs for .note.gnu.property notes

elftools/common/py3compat.py
elftools/elf/descriptions.py
elftools/elf/enums.py
elftools/elf/notes.py
elftools/elf/structs.py
scripts/readelf.py
test/run_readelf_tests.py
test/testfiles_for_readelf/note_gnu_property.S [new file with mode: 0644]
test/testfiles_for_readelf/note_gnu_property.elf [new file with mode: 0755]
test/testfiles_for_readelf/note_gnu_property.o.elf [new file with mode: 0644]

index 7a823a697a168d19c12ff339775c628456474436..259926440a4340b81812f21f845d830567685f7f 100644 (file)
@@ -21,7 +21,11 @@ if PY3:
     # and strings are different types and bytes hold numeric values when
     # iterated over.
 
-    def bytes2hex(b): return b.hex()
+    def bytes2hex(b, sep=''):
+        if not sep:
+            return b.hex()
+        return sep.join(map('{:02x}'.format, b))
+
     def bytes2str(b): return b.decode('latin-1')
     def str2bytes(s): return s.encode('latin-1')
     def int2byte(i): return bytes((i,))
@@ -42,7 +46,12 @@ else:
     import cStringIO
     StringIO = BytesIO = cStringIO.StringIO
 
-    def bytes2hex(b): return b.encode('hex')
+    def bytes2hex(b, sep=''):
+        res = b.encode('hex')
+        if not sep:
+            return res
+        return sep.join(res[i:i+2] for i in range(0, len(res), 2))
+
     def bytes2str(b): return b
     def str2bytes(s): return s
     int2byte = chr
index 9b89415e83700dd5ab31fc2e08714855543ffef1..27e23bed143146a3100ae7471d544f2f4f3f4cf4 100644 (file)
@@ -202,8 +202,10 @@ def describe_note(x):
         desc = '\n    Build ID: %s' % (n_desc)
     elif x['n_type'] == 'NT_GNU_GOLD_VERSION':
         desc = '\n    Version: %s' % (n_desc)
+    elif x['n_type'] == 'NT_GNU_PROPERTY_TYPE_0':
+        desc = '\n      Properties: ' + describe_note_gnu_properties(x['n_desc'])
     else:
-        desc = '\n    description data: {}'.format(bytes2hex(n_desc))
+        desc = '\n      description data: {}'.format(bytes2hex(n_desc))
 
     if x['n_type'] == 'NT_GNU_ABI_TAG' and x['n_name'] == 'Android':
         note_type = 'NT_VERSION'
@@ -244,6 +246,29 @@ def describe_attr_tag_arm(tag, val, extra):
         return _DESCR_ATTR_TAG_ARM[tag] + d_entry[val]
 
 
+def describe_note_gnu_properties(properties):
+    descriptions = []
+    for prop in properties:
+        t, d, sz = prop.pr_type, prop.pr_data, prop.pr_datasz
+        if t == 'GNU_PROPERTY_STACK_SIZE':
+            if type(d) is int:
+                prop_desc = 'stack size: 0x%x' % d
+            else:
+                prop_desc = 'stack size: <corrupt length: 0x%x>' % sz
+        elif t == 'GNU_PROPERTY_NO_COPY_ON_PROTECTED':
+            if sz != 0:
+                prop_desc = ' <corrupt length: 0x%x>' % sz
+            else:
+                prop_desc = 'no copy on protected'
+        elif _DESCR_NOTE_GNU_PROPERTY_TYPE_LOPROC <= t <= _DESCR_NOTE_GNU_PROPERTY_TYPE_HIPROC:
+            prop_desc = '<processor-specific type 0x%x data: %s >' % (t, bytes2hex(d, sep=' '))
+        elif _DESCR_NOTE_GNU_PROPERTY_TYPE_LOUSER <= t <= _DESCR_NOTE_GNU_PROPERTY_TYPE_HIUSER:
+            prop_desc = '<application-specific type 0x%x data: %s >' % (t, bytes2hex(d, sep=' '))
+        else:
+            prop_desc = '<unknown type 0x%x data: %s >' % (t, bytes2hex(d, sep=' '))
+        descriptions.append(prop_desc)
+    return '\n        '.join(descriptions)
+
 #-------------------------------------------------------------------------------
 _unknown = '<unknown>'
 
@@ -331,6 +356,7 @@ _DESCR_P_TYPE = dict(
     PT_GNU_EH_FRAME='GNU_EH_FRAME',
     PT_GNU_STACK='GNU_STACK',
     PT_GNU_RELRO='GNU_RELRO',
+    PT_GNU_PROPERTY='GNU_PROPERTY',
     PT_ARM_ARCHEXT='ARM_ARCHEXT',
     PT_ARM_EXIDX='EXIDX',  # binutils calls this EXIDX, not ARM_EXIDX
     PT_AARCH64_ARCHEXT='AARCH64_ARCHEXT',
@@ -525,6 +551,7 @@ _DESCR_NOTE_N_TYPE = dict(
     NT_GNU_HWCAP='DSO-supplied software HWCAP info',
     NT_GNU_BUILD_ID='unique build ID bitstring',
     NT_GNU_GOLD_VERSION='gold version',
+    NT_GNU_PROPERTY_TYPE_0='program properties'
 )
 
 
@@ -538,6 +565,13 @@ _DESCR_NOTE_ABI_TAG_OS = dict(
     ELF_NOTE_OS_SYLLABLE='Syllable',
 )
 
+# Values in GNU .note.gnu.property notes (n_type=='NT_GNU_PROPERTY_TYPE_0') have
+# different formats which need to be parsed/described differently
+_DESCR_NOTE_GNU_PROPERTY_TYPE_LOPROC=0xc0000000
+_DESCR_NOTE_GNU_PROPERTY_TYPE_HIPROC=0xdfffffff
+_DESCR_NOTE_GNU_PROPERTY_TYPE_LOUSER=0xe0000000
+_DESCR_NOTE_GNU_PROPERTY_TYPE_HIUSER=0xffffffff
+
 def _reverse_dict(d, low_priority=()):
     """
     This is a tiny helper function to "reverse" the keys/values of a dictionary
index 56636e7fa60fea1bf915ed054ef3e3be34c5aed0..201ad96433af7761c56502bbdf7995b63286e64e 100644 (file)
@@ -839,6 +839,7 @@ ENUM_NOTE_N_TYPE = dict(
     NT_GNU_HWCAP=2,
     NT_GNU_BUILD_ID=3,
     NT_GNU_GOLD_VERSION=4,
+    NT_GNU_PROPERTY_TYPE_0=5,
     _default_=Pass,
 )
 
@@ -865,6 +866,13 @@ ENUM_NOTE_ABI_TAG_OS = dict(
     _default_=Pass,
 )
 
+# Values in GNU .note.gnu.property notes (n_type=='NT_GNU_PROPERTY_TYPE_0')
+ENUM_NOTE_GNU_PROPERTY_TYPE = dict(
+    GNU_PROPERTY_STACK_SIZE=1,
+    GNU_PROPERTY_NO_COPY_ON_PROTECTED=2,
+    _default_=Pass,
+)
+
 ENUM_RELOC_TYPE_ARM = dict(
     R_ARM_NONE=0,
     R_ARM_PC24=1,
index 382da94e9d730d3cce5c1796e5d1030f173ed7d6..13895364760f01f46a77489dc64a6d8d4630b5ca 100644 (file)
@@ -47,6 +47,14 @@ def iter_notes(elffile, offset, size):
             note['n_desc'] = struct_parse(elffile.structs.Elf_Nt_File,
                                           elffile.stream,
                                           offset)
+        elif note['n_type'] == 'NT_GNU_PROPERTY_TYPE_0':
+            off = offset
+            props = []
+            while off < end:
+                p = struct_parse(elffile.structs.Elf_Prop, elffile.stream, off)
+                off += roundup(p.pr_datasz + 8, 2 if elffile.elfclass == 32 else 3)
+                props.append(p)
+            note['n_desc'] = props
         else:
             note['n_desc'] = desc_data
         offset += roundup(note['n_descsz'], 2)
index 5be009b5ec3047a8604308ad847b843e7978ea9e..f25cf782b53f21d613ebab27328a1e90df2a3f2f 100644 (file)
@@ -7,13 +7,16 @@
 # Eli Bendersky (eliben@gmail.com)
 # This code is in the public domain
 #-------------------------------------------------------------------------------
+from elftools.construct.macros import AlignedStruct, IfThenElse, UNInt8
 from ..construct import (
     UBInt8, UBInt16, UBInt32, UBInt64,
     ULInt8, ULInt16, ULInt32, ULInt64,
     SBInt32, SLInt32, SBInt64, SLInt64,
-    Struct, Array, Enum, Padding, BitStruct, BitField, Value, String, CString
+    Struct, Array, Enum, Padding, BitStruct, BitField, Value, String, CString,
+    Switch, Field
     )
 from ..common.construct_utils import ULEB128
+from ..common.utils import roundup
 from .enums import *
 
 
@@ -102,6 +105,7 @@ class ELFStructs(object):
         self._create_gnu_verdef()
         self._create_gnu_versym()
         self._create_gnu_abi()
+        self._create_gnu_property()
         self._create_note(e_type)
         self._create_stabs()
         self._create_arm_attributes()
@@ -371,6 +375,28 @@ class ELFStructs(object):
             self.Elf_word('abi_tiny'),
         )
 
+    def _create_gnu_property(self):
+        # Structure of GNU property notes is documented in
+        # https://github.com/hjl-tools/linux-abi/wiki/linux-abi-draft.pdf
+        def roundup_padding(ctx):
+            if self.elfclass == 32:
+                return roundup(ctx.pr_datasz, 2) - ctx.pr_datasz
+            return roundup(ctx.pr_datasz, 3) - ctx.pr_datasz
+
+        self.Elf_Prop = Struct('Elf_Prop',
+            Enum(self.Elf_word('pr_type'), **ENUM_NOTE_GNU_PROPERTY_TYPE),
+            self.Elf_word('pr_datasz'),
+            Switch('pr_data',
+                lambda ctx: (ctx.pr_type, ctx.pr_datasz, self.elfclass),
+                {
+                    ('GNU_PROPERTY_STACK_SIZE', 4, 32): self.Elf_word('pr_data'),
+                    ('GNU_PROPERTY_STACK_SIZE', 8, 64): self.Elf_word64('pr_data')
+                },
+                default=Field('pr_data', lambda ctx: ctx.pr_datasz)
+            ),
+            Padding(roundup_padding)
+        )
+
     def _create_note(self, e_type=None):
         # Structure of "PT_NOTE" section
 
index 249d99a5d976d791606c1afa1798c6fd0348d526..80d565050fbf463031dfc56563275e688d2c75fa 100755 (executable)
@@ -495,7 +495,7 @@ class ReadElf(object):
                 for note in section.iter_notes():
                       self._emitline("\nDisplaying notes found in: {}".format(
                           section.name))
-                      self._emitline('  Owner                 Data size Description')
+                      self._emitline('  Owner                 Data size       Description')
                       self._emitline('  %s %s\t%s' % (
                           note['n_name'].ljust(20),
                           self._format_hex(note['n_descsz'], fieldsize=8),
index 67addf728655b856ada1ca7e31949c991bf74396..96447b4ab29d1e42ffc8b792d458da1e312c8175 100755 (executable)
@@ -140,6 +140,9 @@ def compare_output(s1, s2):
         if 'symbol table' in lines1[i]:
             flag_after_symtable = True
 
+        # readelf spelling error for GNU property notes
+        lines1[i] = lines1[i].replace('procesor-specific type', 'processor-specific type')
+
         # Compare ignoring whitespace
         lines1_parts = lines1[i].split()
         lines2_parts = lines2[i].split()
@@ -178,6 +181,12 @@ def compare_output(s1, s2):
             elif (  'unknown at value' in lines1[i] and
                     'dw_at_apple' in lines2[i]):
                 ok = True
+            elif 'loos+0x474e553' in lines1[i]:
+                # readelf v2.29 does not know about PT_GNU_PROPERTY apparently
+                ok = lines2_parts[0] == 'gnu_property'
+            elif len(lines1_parts) == 3 and lines1_parts[2] == 'nt_gnu_property_type_0':
+                # readelf does not seem to print a readable description for this
+                ok = lines1_parts == lines2_parts[:3]
             else:
                 for s in ('t (tls)', 'l (large)'):
                     if s in lines1[i] or s in lines2[i]:
diff --git a/test/testfiles_for_readelf/note_gnu_property.S b/test/testfiles_for_readelf/note_gnu_property.S
new file mode 100644 (file)
index 0000000..0b11b6b
--- /dev/null
@@ -0,0 +1,91 @@
+/**
+ * Test ELF for .note.gnu.property, built on x86-64.
+ *
+ * Object file:
+ *     gcc -c note_gnu_property.S -o note_gnu_property.o.elf
+ *
+ * ELF executable (to also have a PT_GNU_PROPERTY program header):
+ *     gcc -DEXE -c note_gnu_property.S -o /tmp/x.o
+ *     ld /tmp/x.o -o note_gnu_property.elf
+ *     strip
+ */
+
+// https://github.com/hjl-tools/linux-abi/wiki/linux-abi-draft.pdf
+#define NT_GNU_PROPERTY_TYPE_0            5
+#define GNU_PROPERTY_STACK_SIZE           1
+#define GNU_PROPERTY_NO_COPY_ON_PROTECTED 2
+#define GNU_PROPERTY_LOPROC               0xc0000000
+#define GNU_PROPERTY_HIPROC               0xdfffffff
+#define GNU_PROPERTY_LOUSER               0xe0000000
+#define GNU_PROPERTY_HIUSER               0xffffffff
+
+// Unknown property types for testing purposes
+#define GNU_PROPERTY_TEST_UNKNOWN         0x12345678
+#define GNU_PROPERTY_TEST_UNKNOWN_PROC    0xc1234567
+#define GNU_PROPERTY_TEST_UNKNOWN_USER    0xe1234567
+
+// https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/x86-64-psABI
+#define GNU_PROPERTY_X86_FEATURE_1_AND    0xc0000002
+#define GNU_PROPERTY_X86_FEATURE_1_IBT    0x00000001
+#define GNU_PROPERTY_X86_FEATURE_1_SHSTK  0x00000002
+
+#ifdef __x86_64__
+#define ALIGN .p2align 3
+#else
+#define ALIGN .p2align 2
+#endif
+
+.section ".text"
+.global _start
+_start:
+       ud2
+
+.section ".note.gnu.property", "a"
+       ALIGN
+       .long 1f - 0f                // n_namesz
+       .long end - 2f               // n_descsz
+       .long NT_GNU_PROPERTY_TYPE_0 // n_type
+0:     .asciz "GNU"                 // n_name
+1:
+       ALIGN
+2:     .long GNU_PROPERTY_STACK_SIZE // pr_type
+       .long 4f - 3f                 // pr_datasz
+3:
+       .dc.a 0x123000
+4:
+       ALIGN
+       .long GNU_PROPERTY_NO_COPY_ON_PROTECTED // pr_type
+       .long 0                                 // pr_datasz
+       ALIGN
+
+// Avoid these if linking to executable, linkers may not recognize them
+#ifndef EXE
+       .long GNU_PROPERTY_TEST_UNKNOWN // pr_type
+       .long 6f-5f                     // pr_datasz
+5:
+       .ascii "hello world"
+6:
+       ALIGN
+       .long GNU_PROPERTY_TEST_UNKNOWN_PROC // pr_type
+       .long 8f-7f                          // pr_datasz
+7:
+       .ascii "foobar"
+8:
+       ALIGN
+       .long GNU_PROPERTY_TEST_UNKNOWN_USER // pr_type
+       .long 10f-9f                         // pr_datasz
+9:
+       .ascii "bazquuz"
+10:
+       ALIGN
+#endif
+
+/* TODO: add support for these later...
+6:     .long GNU_PROPERTY_X86_FEATURE_1_AND // pr_type.
+       .long 8f - 7f                        // pr_datasz
+7:
+       .long GNU_PROPERTY_X86_FEATURE_1_IBT|GNU_PROPERTY_X86_FEATURE_1_SHSTK
+8:
+       ALIGN
+*/
+end:
diff --git a/test/testfiles_for_readelf/note_gnu_property.elf b/test/testfiles_for_readelf/note_gnu_property.elf
new file mode 100755 (executable)
index 0000000..c97e060
Binary files /dev/null and b/test/testfiles_for_readelf/note_gnu_property.elf differ
diff --git a/test/testfiles_for_readelf/note_gnu_property.o.elf b/test/testfiles_for_readelf/note_gnu_property.o.elf
new file mode 100644 (file)
index 0000000..2f6f6b8
Binary files /dev/null and b/test/testfiles_for_readelf/note_gnu_property.o.elf differ