From 5ee4158a08a05ddb24f009363cc4337b8ecaad09 Mon Sep 17 00:00:00 2001 From: Marco Bonelli Date: Tue, 7 Dec 2021 15:08:54 +0100 Subject: [PATCH] Add support for .note.gnu.property notes section (#386) * 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 | 13 ++- elftools/elf/descriptions.py | 36 ++++++- elftools/elf/enums.py | 8 ++ elftools/elf/notes.py | 8 ++ elftools/elf/structs.py | 28 +++++- scripts/readelf.py | 2 +- test/run_readelf_tests.py | 9 ++ .../testfiles_for_readelf/note_gnu_property.S | 91 ++++++++++++++++++ .../note_gnu_property.elf | Bin 0 -> 4792 bytes .../note_gnu_property.o.elf | Bin 0 -> 928 bytes 10 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 test/testfiles_for_readelf/note_gnu_property.S create mode 100755 test/testfiles_for_readelf/note_gnu_property.elf create mode 100644 test/testfiles_for_readelf/note_gnu_property.o.elf diff --git a/elftools/common/py3compat.py b/elftools/common/py3compat.py index 7a823a6..2599264 100644 --- a/elftools/common/py3compat.py +++ b/elftools/common/py3compat.py @@ -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 diff --git a/elftools/elf/descriptions.py b/elftools/elf/descriptions.py index 9b89415..27e23be 100644 --- a/elftools/elf/descriptions.py +++ b/elftools/elf/descriptions.py @@ -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: ' % sz + elif t == 'GNU_PROPERTY_NO_COPY_ON_PROTECTED': + if sz != 0: + prop_desc = ' ' % sz + else: + prop_desc = 'no copy on protected' + elif _DESCR_NOTE_GNU_PROPERTY_TYPE_LOPROC <= t <= _DESCR_NOTE_GNU_PROPERTY_TYPE_HIPROC: + prop_desc = '' % (t, bytes2hex(d, sep=' ')) + elif _DESCR_NOTE_GNU_PROPERTY_TYPE_LOUSER <= t <= _DESCR_NOTE_GNU_PROPERTY_TYPE_HIUSER: + prop_desc = '' % (t, bytes2hex(d, sep=' ')) + else: + prop_desc = '' % (t, bytes2hex(d, sep=' ')) + descriptions.append(prop_desc) + return '\n '.join(descriptions) + #------------------------------------------------------------------------------- _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 diff --git a/elftools/elf/enums.py b/elftools/elf/enums.py index 56636e7..201ad96 100644 --- a/elftools/elf/enums.py +++ b/elftools/elf/enums.py @@ -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, diff --git a/elftools/elf/notes.py b/elftools/elf/notes.py index 382da94..1389536 100644 --- a/elftools/elf/notes.py +++ b/elftools/elf/notes.py @@ -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) diff --git a/elftools/elf/structs.py b/elftools/elf/structs.py index 5be009b..f25cf78 100644 --- a/elftools/elf/structs.py +++ b/elftools/elf/structs.py @@ -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 diff --git a/scripts/readelf.py b/scripts/readelf.py index 249d99a..80d5650 100755 --- a/scripts/readelf.py +++ b/scripts/readelf.py @@ -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), diff --git a/test/run_readelf_tests.py b/test/run_readelf_tests.py index 67addf7..96447b4 100755 --- a/test/run_readelf_tests.py +++ b/test/run_readelf_tests.py @@ -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 index 0000000..0b11b6b --- /dev/null +++ b/test/testfiles_for_readelf/note_gnu_property.S @@ -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 index 0000000000000000000000000000000000000000..c97e060a501e20aa668636c23825fc47067ab8f2 GIT binary patch literal 4792 zcmeHLyH3ME5L`PC0u%`p2nm5ADk_J9A{BB2q(C%?+Hw>NnmlA5Qd04olr($*UjVo3 z9dM!`q*N4Z>24o4yLXvXJ1ZQbVxypCj*~ zqO=2T4NIL8%;^TZ8k61!8Cju3m-Y$XWa_f$aq=9^^64zg-GU;Q&E3+AHVck?*_L0> z55O;U4d@r>4jlt=V7>P#0^u=iKr{{!~2^~5P-7)E}@EC7T~j%j7&O$KJ;LByR7LVjBst>x_x*w z|L7BSJ|GVr;{oPR7Xy+^MViW^52@0`Fq7fQ_a=V5N*QwnfbmZcR8Si&_2 z7UwyzOlrXbhdSiY~b$2akXnUsbln%^H1#B2|V=mr^wSg zSXY0BT6zb5pLdue8yjCa^X971^zLhk{<1X=>3nE8{ek7T5dGeX8rea2Y@Gvt{(U6c N^v|~N$_Xv{`d|OFF*5)F literal 0 HcmV?d00001 -- 2.30.2