Skip to content

Commit 1c598fd

Browse files
committed
Replace pyelftools dependency with custom minimal ELF parser in LKM plugin
1 parent 1c40b49 commit 1c598fd

5 files changed

Lines changed: 75 additions & 16 deletions

File tree

pyproject-scancode-toolkit-mini.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ dependencies = [
8888
"plugincode >= 32.0.0",
8989
"publicsuffix2",
9090
"pyahocorasick >= 2.3.0",
91-
"pyelftools >= 0.32",
9291
"pygmars >= 1.0.0",
9392
"pygments >= 1.0.0",
9493
"pymaven_patch >= 0.2.8",

pyproject-scancode-toolkit.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ dependencies = [
8888
"plugincode >= 32.0.0",
8989
"publicsuffix2",
9090
"pyahocorasick >= 2.3.0",
91-
"pyelftools >= 0.32",
9291
"pygmars >= 1.0.0",
9392
"pygments >= 1.0.0",
9493
"pymaven_patch >= 0.2.8",

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ dependencies = [
8989
"plugincode >= 32.0.0",
9090
"publicsuffix2",
9191
"pyahocorasick >= 2.3.0",
92-
"pyelftools >= 0.32",
9392
"pygmars >= 1.0.0",
9493
"pygments >= 1.0.0",
9594
"pymaven_patch >= 0.2.8",

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ publicsuffix2==2.20191221
6363
py==1.11.0
6464
pyahocorasick==2.3.1
6565
pycparser==2.23
66-
pyelftools==0.32
6766
pygmars==1.0.0
6867
Pygments==2.13.0
6968
pymaven-patch==0.3.2

src/scancode/plugin_lkm.py

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
from commoncode.cliutils import OTHER_SCAN_GROUP
1616
from commoncode.cliutils import PluggableCommandLineOption
17-
from elftools.common.exceptions import ELFError
18-
from elftools.elf.elffile import ELFFile
1917
from plugincode.scan import ScanPlugin
2018
from plugincode.scan import scan_impl
2119

@@ -73,25 +71,90 @@ def scan_linux_kernel_module(location, **kwargs):
7371

7472
def extract_modinfo(location: str) -> Dict[str, List[str]]:
7573
"""
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.
7776
7877
Return '.modinfo' metadata as a mapping of keys to lists of values.
7978
8079
Multiple values are preserved because fields such as 'author', 'alias',
8180
and 'firmware' may occur more than once.
8281
"""
82+
import struct
83+
8384
metadata: Dict[str, List[str]] = {}
85+
raw_bytes = b''
8486

8587
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:
95158
return {}
96159

97160
# Entries in .modinfo are NUL-terminated key=value strings.

0 commit comments

Comments
 (0)