Skip to content

Commit a104528

Browse files
Refactor Linux kernel module metadata as a file scan plugin
Signed-off-by: OctavioValdiviaMendoza <octavio.valdiviamendoza@sjsu.edu>
1 parent 01188e7 commit a104528

6 files changed

Lines changed: 133 additions & 212 deletions

File tree

CHANGELOG.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Next release
88
``licensedcode-data``.
99
https://github.com/aboutcode-org/scancode-toolkit/pull/5056
1010

11-
- Add a package data handler for compiled Linux Kernel Module (``.ko``)
11+
- Add a file-level scan plugin for compiled Linux Kernel Module (``.ko``)
1212
files that extracts metadata from the ELF ``.modinfo`` section.
1313

1414
v33.0.0rc1 - 2026-05-14

pyproject-scancode-toolkit.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ facet = "summarycode.facet:AddFacet"
267267
# module for details and doc.
268268
[project.entry-points.scancode_scan]
269269
info = "scancode.plugin_info:InfoScanner"
270+
lkm = "scancode.plugin_lkm:LinuxKernelModuleScanner"
270271
licenses = "licensedcode.plugin_license:LicenseScanner"
271272
copyrights = "cluecode.plugin_copyright:CopyrightScanner"
272273
packages = "packagedcode.plugin_package:PackageScanner"

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ facet = "summarycode.facet:AddFacet"
272272
# module for details and doc.
273273
[project.entry-points.scancode_scan]
274274
info = "scancode.plugin_info:InfoScanner"
275+
lkm = "scancode.plugin_lkm:LinuxKernelModuleScanner"
275276
licenses = "licensedcode.plugin_license:LicenseScanner"
276277
copyrights = "cluecode.plugin_copyright:CopyrightScanner"
277278
packages = "packagedcode.plugin_package:PackageScanner"

src/packagedcode/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from packagedcode import godeps
2727
from packagedcode import golang
2828
from packagedcode import haxe
29-
from packagedcode import lkm
3029
from packagedcode import maven
3130
from packagedcode import misc
3231
from packagedcode import npm
@@ -234,8 +233,6 @@
234233
debian.DebianInstalledMd5sumFilelistHandler,
235234
debian.DebianInstalledStatusDatabaseHandler,
236235

237-
lkm.LinuxKernelModuleHandler,
238-
239236
rpm.RpmLicenseFilesHandler,
240237
rpm.RpmMarinerContainerManifestHandler,
241238
]

src/packagedcode/lkm.py

Lines changed: 0 additions & 208 deletions
This file was deleted.

src/scancode/plugin_lkm.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# ScanCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/nexB/scancode-toolkit for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
from typing import Dict
11+
from typing import List
12+
13+
import attr
14+
15+
from commoncode.cliutils import OTHER_SCAN_GROUP
16+
from commoncode.cliutils import PluggableCommandLineOption
17+
from elftools.common.exceptions import ELFError
18+
from elftools.elf.elffile import ELFFile
19+
from plugincode.scan import ScanPlugin
20+
from plugincode.scan import scan_impl
21+
22+
23+
@scan_impl
24+
class LinuxKernelModuleScanner(ScanPlugin):
25+
"""
26+
Scan Linux kernel module files for metadata stored in their ELF
27+
'.modinfo' section.
28+
"""
29+
30+
resource_attributes = dict(
31+
linux_kernel_module=attr.ib(default=None, repr=False),
32+
)
33+
34+
run_order = 9
35+
sort_order = 9
36+
37+
options = [
38+
PluggableCommandLineOption(
39+
('--lkm',),
40+
is_flag=True,
41+
default=False,
42+
help='Scan Linux kernel module files for .modinfo metadata.',
43+
help_group=OTHER_SCAN_GROUP,
44+
)
45+
]
46+
47+
def is_enabled(self, lkm, **kwargs):
48+
return lkm
49+
50+
def get_scanner(self, **kwargs):
51+
return scan_linux_kernel_module
52+
53+
54+
def scan_linux_kernel_module(location, **kwargs):
55+
"""
56+
Return a mapping of Linux kernel module metadata found in the '.ko' file
57+
at 'location'. Return an empty mapping for other files and for files that
58+
do not contain usable '.modinfo' metadata.
59+
"""
60+
if not location.lower().endswith('.ko'):
61+
return {}
62+
63+
metadata = extract_modinfo(location)
64+
if not metadata:
65+
return {}
66+
67+
# The .modinfo "depends" value is a comma-separated list of required
68+
# kernel module names, not a list of package-management dependencies.
69+
metadata['depends'] = get_dependency_names(metadata)
70+
71+
return dict(linux_kernel_module=metadata)
72+
73+
74+
def extract_modinfo(location: str) -> Dict[str, List[str]]:
75+
"""
76+
Extract '.modinfo' metadata from the Linux kernel module file at 'location'.
77+
78+
Return '.modinfo' metadata as a mapping of keys to lists of values.
79+
80+
Multiple values are preserved because fields such as 'author', 'alias',
81+
and 'firmware' may occur more than once.
82+
"""
83+
metadata: Dict[str, List[str]] = {}
84+
85+
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):
95+
return {}
96+
97+
# Entries in .modinfo are NUL-terminated key=value strings.
98+
for raw_entry in raw_bytes.split(b'\x00'):
99+
if not raw_entry:
100+
continue
101+
102+
entry = raw_entry.decode('utf-8', errors='replace')
103+
if '=' not in entry:
104+
continue
105+
106+
key, value = entry.split('=', 1)
107+
if not key:
108+
continue
109+
110+
metadata.setdefault(key, []).append(value)
111+
112+
return metadata
113+
114+
115+
def get_dependency_names(metadata: Dict[str, List[str]]) -> List[str]:
116+
"""
117+
Returns a list of strings, each being a kernel module name
118+
Normalized fields to a list of strings derived from the '.modinfo' metadata. The 'depends' field is a
119+
comma-separated list of required kernel module names
120+
"""
121+
dependency_names = []
122+
123+
for depends_entry in metadata.get('depends', []):
124+
dependency_names.extend(
125+
dependency.strip()
126+
for dependency in depends_entry.split(',')
127+
if dependency.strip()
128+
)
129+
130+
return dependency_names

0 commit comments

Comments
 (0)