|
14 | 14 |
|
15 | 15 | from commoncode.cliutils import OTHER_SCAN_GROUP |
16 | 16 | from commoncode.cliutils import PluggableCommandLineOption |
17 | | -from elftools.common.exceptions import ELFError |
18 | | -from elftools.elf.elffile import ELFFile |
19 | 17 | from plugincode.scan import ScanPlugin |
20 | 18 | from plugincode.scan import scan_impl |
21 | 19 |
|
@@ -73,25 +71,90 @@ def scan_linux_kernel_module(location, **kwargs): |
73 | 71 |
|
74 | 72 | def extract_modinfo(location: str) -> Dict[str, List[str]]: |
75 | 73 | """ |
76 | | - Extract '.modinfo' metadata from the Linux kernel module file at 'location'. |
| 74 | + Extract '.modinfo' metadata from the Linux kernel module file at 'location' |
| 75 | + using a custom minimal ELF parser. |
77 | 76 |
|
78 | 77 | Return '.modinfo' metadata as a mapping of keys to lists of values. |
79 | 78 |
|
80 | 79 | Multiple values are preserved because fields such as 'author', 'alias', |
81 | 80 | and 'firmware' may occur more than once. |
82 | 81 | """ |
| 82 | + import struct |
| 83 | + |
83 | 84 | metadata: Dict[str, List[str]] = {} |
| 85 | + raw_bytes = b'' |
84 | 86 |
|
85 | 87 | try: |
86 | | - with open(location, 'rb') as module_file: |
87 | | - elf_file = ELFFile(module_file) |
88 | | - modinfo_section = elf_file.get_section_by_name('.modinfo') |
89 | | - if modinfo_section is None: |
90 | | - return {} |
91 | | - |
92 | | - raw_bytes = modinfo_section.data() |
93 | | - |
94 | | - except (ELFError, OSError): |
| 88 | + with open(location, 'rb') as f: |
| 89 | + e_ident = f.read(16) |
| 90 | + if len(e_ident) == 16 and e_ident[0:4] == b'\x7fELF': |
| 91 | + ei_class = e_ident[4] # 1 = 32-bit, 2 = 64-bit |
| 92 | + ei_data = e_ident[5] # 1 = LSB, 2 = MSB |
| 93 | + |
| 94 | + if ei_class in (1, 2) and ei_data in (1, 2): |
| 95 | + endian = '<' if ei_data == 1 else '>' |
| 96 | + |
| 97 | + if ei_class == 1: |
| 98 | + # 32-bit ELF |
| 99 | + f.seek(32) |
| 100 | + header_data = f.read(20) |
| 101 | + if len(header_data) == 20: |
| 102 | + e_shoff, _, _, _, _, e_shentsize, e_shnum, e_shstrndx = struct.unpack( |
| 103 | + endian + 'IIHHHHHH', header_data |
| 104 | + ) |
| 105 | + else: |
| 106 | + # 64-bit ELF |
| 107 | + f.seek(40) |
| 108 | + header_data = f.read(24) |
| 109 | + if len(header_data) == 24: |
| 110 | + e_shoff, _, _, _, _, e_shentsize, e_shnum, e_shstrndx = struct.unpack( |
| 111 | + endian + 'QIHHHHHH', header_data |
| 112 | + ) |
| 113 | + |
| 114 | + if e_shnum > 0 and e_shentsize > 0: |
| 115 | + f.seek(e_shoff) |
| 116 | + section_headers_data = f.read(e_shnum * e_shentsize) |
| 117 | + if len(section_headers_data) == e_shnum * e_shentsize: |
| 118 | + |
| 119 | + # Helper to parse section header at index |
| 120 | + def get_section_header(idx): |
| 121 | + offset = idx * e_shentsize |
| 122 | + entry_data = section_headers_data[offset:offset + e_shentsize] |
| 123 | + if len(entry_data) < e_shentsize: |
| 124 | + return None |
| 125 | + if ei_class == 1: |
| 126 | + sh_name, _, _, _, sh_offset, sh_size = struct.unpack( |
| 127 | + endian + 'IIIIII', entry_data[:24] |
| 128 | + ) |
| 129 | + else: |
| 130 | + sh_name, _, _, _, sh_offset, sh_size = struct.unpack( |
| 131 | + endian + 'IIQQQQ', entry_data[:40] |
| 132 | + ) |
| 133 | + return sh_name, sh_offset, sh_size |
| 134 | + |
| 135 | + shstr_header = get_section_header(e_shstrndx) |
| 136 | + if shstr_header: |
| 137 | + _, shstr_offset, shstr_size = shstr_header |
| 138 | + f.seek(shstr_offset) |
| 139 | + shstr_table = f.read(shstr_size) |
| 140 | + if len(shstr_table) == shstr_size: |
| 141 | + for i in range(e_shnum): |
| 142 | + header = get_section_header(i) |
| 143 | + if not header: |
| 144 | + continue |
| 145 | + sh_name, sh_offset, sh_size = header |
| 146 | + |
| 147 | + end = shstr_table.find(b'\x00', sh_name) |
| 148 | + if end != -1: |
| 149 | + name = shstr_table[sh_name:end].decode('utf-8', errors='ignore') |
| 150 | + else: |
| 151 | + name = shstr_table[sh_name:].decode('utf-8', errors='ignore') |
| 152 | + |
| 153 | + if name == '.modinfo': |
| 154 | + f.seek(sh_offset) |
| 155 | + raw_bytes = f.read(sh_size) |
| 156 | + break |
| 157 | + except Exception: |
95 | 158 | return {} |
96 | 159 |
|
97 | 160 | # Entries in .modinfo are NUL-terminated key=value strings. |
|
0 commit comments