pybinaryguard 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pybinaryguard/__init__.py +78 -0
- pybinaryguard/__main__.py +7 -0
- pybinaryguard/_compat/__init__.py +0 -0
- pybinaryguard/agent/__init__.py +63 -0
- pybinaryguard/agent/guard.py +232 -0
- pybinaryguard/agent/recommender.py +283 -0
- pybinaryguard/agent/schema.py +200 -0
- pybinaryguard/agent/simulator.py +430 -0
- pybinaryguard/agent/tool_interface.py +474 -0
- pybinaryguard/analyzers/__init__.py +209 -0
- pybinaryguard/analyzers/base.py +40 -0
- pybinaryguard/analyzers/dependency_analyzer.py +336 -0
- pybinaryguard/analyzers/elf_analyzer.py +754 -0
- pybinaryguard/analyzers/symbol_analyzer.py +280 -0
- pybinaryguard/analyzers/wheel_analyzer.py +308 -0
- pybinaryguard/cli/__init__.py +5 -0
- pybinaryguard/cli/commands.py +414 -0
- pybinaryguard/cli/formatters.py +720 -0
- pybinaryguard/cli/main.py +250 -0
- pybinaryguard/diagnostics/__init__.py +13 -0
- pybinaryguard/diagnostics/explainer.py +356 -0
- pybinaryguard/diagnostics/findings.py +146 -0
- pybinaryguard/diagnostics/suggestions.py +508 -0
- pybinaryguard/frameworks/__init__.py +20 -0
- pybinaryguard/frameworks/onnxruntime.py +214 -0
- pybinaryguard/frameworks/pytorch.py +223 -0
- pybinaryguard/frameworks/tensorflow.py +266 -0
- pybinaryguard/frameworks/tensorrt.py +189 -0
- pybinaryguard/models/__init__.py +19 -0
- pybinaryguard/models/enums.py +102 -0
- pybinaryguard/models/finding.py +109 -0
- pybinaryguard/models/package.py +121 -0
- pybinaryguard/models/system.py +118 -0
- pybinaryguard/plugins/__init__.py +19 -0
- pybinaryguard/plugins/contrib/__init__.py +7 -0
- pybinaryguard/plugins/contrib/gstreamer.py +109 -0
- pybinaryguard/plugins/contrib/jetson.py +385 -0
- pybinaryguard/plugins/contrib/opencv.py +306 -0
- pybinaryguard/plugins/contrib/tensorrt.py +426 -0
- pybinaryguard/plugins/hooks.py +273 -0
- pybinaryguard/plugins/loader.py +190 -0
- pybinaryguard/predictor/__init__.py +23 -0
- pybinaryguard/predictor/dependency_graph.py +226 -0
- pybinaryguard/predictor/linker_simulator.py +161 -0
- pybinaryguard/predictor/predictor.py +144 -0
- pybinaryguard/predictor/resolver.py +282 -0
- pybinaryguard/probes/__init__.py +73 -0
- pybinaryguard/probes/base.py +45 -0
- pybinaryguard/probes/board_probe.py +248 -0
- pybinaryguard/probes/cpu_probe.py +176 -0
- pybinaryguard/probes/glibc_probe.py +215 -0
- pybinaryguard/probes/gpu_probe.py +430 -0
- pybinaryguard/probes/library_probe.py +132 -0
- pybinaryguard/probes/os_probe.py +227 -0
- pybinaryguard/probes/python_probe.py +135 -0
- pybinaryguard/probes/toolchain_probe.py +89 -0
- pybinaryguard/probes/venv_probe.py +111 -0
- pybinaryguard/profiles/__init__.py +12 -0
- pybinaryguard/profiles/engine.py +343 -0
- pybinaryguard/rules/__init__.py +24 -0
- pybinaryguard/rules/base.py +57 -0
- pybinaryguard/rules/builtin/__init__.py +136 -0
- pybinaryguard/rules/builtin/arch_rules.py +86 -0
- pybinaryguard/rules/builtin/board_profile_rules.py +282 -0
- pybinaryguard/rules/builtin/container_rules.py +170 -0
- pybinaryguard/rules/builtin/cpu_rules.py +204 -0
- pybinaryguard/rules/builtin/cuda_rules.py +760 -0
- pybinaryguard/rules/builtin/dependency_rules.py +278 -0
- pybinaryguard/rules/builtin/framework_rules.py +252 -0
- pybinaryguard/rules/builtin/glibc_rules.py +320 -0
- pybinaryguard/rules/builtin/numpy_rules.py +110 -0
- pybinaryguard/rules/builtin/predictive_rules.py +165 -0
- pybinaryguard/rules/builtin/python_abi_rules.py +259 -0
- pybinaryguard/rules/builtin/source_build_rules.py +150 -0
- pybinaryguard/rules/builtin/venv_rules.py +159 -0
- pybinaryguard/rules/engine.py +123 -0
- pybinaryguard/scanner.py +904 -0
- pybinaryguard/scoring/__init__.py +19 -0
- pybinaryguard/scoring/engine.py +416 -0
- pybinaryguard/snapshot/__init__.py +20 -0
- pybinaryguard/snapshot/generator.py +168 -0
- pybinaryguard/snapshot/lockfile.py +152 -0
- pybinaryguard/snapshot/verifier.py +315 -0
- pybinaryguard/validators/__init__.py +7 -0
- pybinaryguard/validators/import_validator.py +231 -0
- pybinaryguard-1.0.0.dist-info/METADATA +876 -0
- pybinaryguard-1.0.0.dist-info/RECORD +91 -0
- pybinaryguard-1.0.0.dist-info/WHEEL +5 -0
- pybinaryguard-1.0.0.dist-info/entry_points.txt +8 -0
- pybinaryguard-1.0.0.dist-info/licenses/LICENSE +21 -0
- pybinaryguard-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""GStreamer pipeline validation plugin.
|
|
2
|
+
|
|
3
|
+
Checks for GStreamer availability and hardware acceleration on embedded boards.
|
|
4
|
+
Activated only when GStreamer libraries are detected on the system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from pybinaryguard.models.enums import Severity
|
|
13
|
+
from pybinaryguard.models.finding import Finding
|
|
14
|
+
from pybinaryguard.models.package import PackageBinaryInfo
|
|
15
|
+
from pybinaryguard.models.system import SystemProfile
|
|
16
|
+
from pybinaryguard.probes.base import ProbeBase
|
|
17
|
+
from pybinaryguard.rules.base import Rule
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GStreamerProbe(ProbeBase):
|
|
21
|
+
"""Detect GStreamer installation and version."""
|
|
22
|
+
|
|
23
|
+
name = "gstreamer"
|
|
24
|
+
|
|
25
|
+
def collect(self) -> Dict[str, Any]:
|
|
26
|
+
"""Check for GStreamer library presence."""
|
|
27
|
+
data: Dict[str, Any] = {}
|
|
28
|
+
|
|
29
|
+
# Check for GStreamer library files
|
|
30
|
+
gst_lib_names = [
|
|
31
|
+
"libgstreamer-1.0.so",
|
|
32
|
+
"libgstreamer-1.0.so.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
for lib_name in gst_lib_names:
|
|
36
|
+
for search_dir in ["/usr/lib", "/usr/lib/aarch64-linux-gnu",
|
|
37
|
+
"/usr/lib/x86_64-linux-gnu", "/usr/local/lib"]:
|
|
38
|
+
path = os.path.join(search_dir, lib_name)
|
|
39
|
+
if os.path.exists(path):
|
|
40
|
+
data["_gstreamer_available"] = True
|
|
41
|
+
return data
|
|
42
|
+
|
|
43
|
+
data["_gstreamer_available"] = False
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
def is_applicable(self) -> bool:
|
|
47
|
+
"""Only run on Linux."""
|
|
48
|
+
import platform
|
|
49
|
+
return platform.system() == "Linux"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class GStreamerMissingRule(Rule):
|
|
53
|
+
"""Check for GStreamer availability on embedded boards with camera use-cases."""
|
|
54
|
+
|
|
55
|
+
rule_id = "GSTREAMER_MISSING"
|
|
56
|
+
description = "GStreamer not found on embedded board"
|
|
57
|
+
|
|
58
|
+
def is_applicable(self, profile: SystemProfile) -> bool:
|
|
59
|
+
return profile.is_embedded_board
|
|
60
|
+
|
|
61
|
+
def evaluate(
|
|
62
|
+
self, profile: SystemProfile, packages: List[PackageBinaryInfo]
|
|
63
|
+
) -> List[Finding]:
|
|
64
|
+
findings: List[Finding] = []
|
|
65
|
+
|
|
66
|
+
# Only relevant if OpenCV or camera-related packages are installed
|
|
67
|
+
camera_packages = {"opencv-python", "opencv-contrib-python", "cv2"}
|
|
68
|
+
has_camera_pkg = any(
|
|
69
|
+
p.package_name.lower().replace("-", "_") in {
|
|
70
|
+
n.replace("-", "_") for n in camera_packages
|
|
71
|
+
}
|
|
72
|
+
for p in packages
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if not has_camera_pkg:
|
|
76
|
+
return findings
|
|
77
|
+
|
|
78
|
+
# Check for GStreamer
|
|
79
|
+
gst_found = False
|
|
80
|
+
for search_dir in ["/usr/lib", "/usr/lib/aarch64-linux-gnu",
|
|
81
|
+
"/usr/lib/x86_64-linux-gnu"]:
|
|
82
|
+
if os.path.exists(os.path.join(search_dir, "libgstreamer-1.0.so.0")):
|
|
83
|
+
gst_found = True
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
if not gst_found:
|
|
87
|
+
findings.append(Finding(
|
|
88
|
+
rule_id=self.rule_id,
|
|
89
|
+
severity=Severity.WARNING,
|
|
90
|
+
title="GStreamer not found on embedded board",
|
|
91
|
+
explanation=(
|
|
92
|
+
"GStreamer is commonly needed for camera pipelines on "
|
|
93
|
+
"embedded boards but was not found on this system."
|
|
94
|
+
),
|
|
95
|
+
suggestion="sudo apt-get install libgstreamer1.0-0 gstreamer1.0-plugins-base",
|
|
96
|
+
confidence=0.7,
|
|
97
|
+
))
|
|
98
|
+
|
|
99
|
+
return findings
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def register(registry: Any) -> None:
|
|
103
|
+
"""Register GStreamer plugin components."""
|
|
104
|
+
# Only activate on Linux
|
|
105
|
+
import platform
|
|
106
|
+
if platform.system() != "Linux":
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
registry.add_rule(GStreamerMissingRule())
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""Jetson platform plugin for PyBinaryGuard.
|
|
2
|
+
|
|
3
|
+
Activates when ``/etc/nv_tegra_release`` exists, indicating an NVIDIA
|
|
4
|
+
Jetson (Tegra) system running Linux for Tegra (L4T).
|
|
5
|
+
|
|
6
|
+
Provides
|
|
7
|
+
--------
|
|
8
|
+
- **JetsonProbe** -- Enhanced Jetson detection: L4T version, JetPack
|
|
9
|
+
mapping, and Tegra SoC model.
|
|
10
|
+
- **JetPackCUDARule** -- Verifies that the installed CUDA version matches
|
|
11
|
+
the expected version for the detected JetPack release.
|
|
12
|
+
- **JetsonX86WheelRule** -- Detects x86-only wheels installed on an ARM
|
|
13
|
+
Jetson board, which will fail at import time.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from pybinaryguard.models.enums import Architecture, Severity
|
|
24
|
+
from pybinaryguard.models.finding import Finding
|
|
25
|
+
from pybinaryguard.models.package import PackageBinaryInfo
|
|
26
|
+
from pybinaryguard.models.system import SystemProfile
|
|
27
|
+
from pybinaryguard.probes.base import ProbeBase
|
|
28
|
+
from pybinaryguard.rules.base import Rule
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from pybinaryguard.plugins.hooks import HookRegistry
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
# -- Mapping tables ---------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
L4T_TO_JETPACK: Dict[str, str] = {
|
|
38
|
+
"32.7.1": "4.6.1",
|
|
39
|
+
"32.7.2": "4.6.2",
|
|
40
|
+
"32.7.3": "4.6.3",
|
|
41
|
+
"32.7.4": "4.6.4",
|
|
42
|
+
"35.1.0": "5.0.2",
|
|
43
|
+
"35.2.1": "5.1",
|
|
44
|
+
"35.3.1": "5.1.1",
|
|
45
|
+
"35.4.1": "5.1.2",
|
|
46
|
+
"35.5.0": "5.1.3",
|
|
47
|
+
"36.2.0": "6.0",
|
|
48
|
+
"36.3.0": "6.0.1",
|
|
49
|
+
"36.4.0": "6.1",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
JETPACK_CUDA: Dict[str, Tuple[int, int]] = {
|
|
53
|
+
"4.6.1": (10, 2),
|
|
54
|
+
"4.6.2": (10, 2),
|
|
55
|
+
"4.6.3": (10, 2),
|
|
56
|
+
"4.6.4": (10, 2),
|
|
57
|
+
"5.0.2": (11, 4),
|
|
58
|
+
"5.1": (11, 4),
|
|
59
|
+
"5.1.1": (11, 4),
|
|
60
|
+
"5.1.2": (11, 4),
|
|
61
|
+
"5.1.3": (11, 4),
|
|
62
|
+
"6.0": (12, 2),
|
|
63
|
+
"6.0.1": (12, 2),
|
|
64
|
+
"6.1": (12, 6),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Known Tegra SoC chip IDs from /sys/module/tegra_fuse/parameters/tegra_chip_id
|
|
68
|
+
_TEGRA_CHIP_NAMES: Dict[str, str] = {
|
|
69
|
+
"0x18": "TX2 (Parker)",
|
|
70
|
+
"0x19": "Xavier (Carmel)",
|
|
71
|
+
"0x21": "Nano/TX1 (Erista)",
|
|
72
|
+
"0x23": "Orin (Ampere)",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_TEGRA_RELEASE_PATH = "/etc/nv_tegra_release"
|
|
76
|
+
_TEGRA_CHIP_ID_PATH = "/sys/module/tegra_fuse/parameters/tegra_chip_id"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# -- Probe ------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class JetsonProbe(ProbeBase):
|
|
83
|
+
"""Enhanced Jetson/Tegra probe.
|
|
84
|
+
|
|
85
|
+
Reads ``/etc/nv_tegra_release`` to determine the L4T version, maps it
|
|
86
|
+
to a JetPack version, and identifies the Tegra SoC model. All data is
|
|
87
|
+
returned as ``SystemProfile`` field values.
|
|
88
|
+
|
|
89
|
+
This probe is read-only and does not modify any system state.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
name = "jetson"
|
|
93
|
+
|
|
94
|
+
def is_applicable(self) -> bool:
|
|
95
|
+
"""Only run on systems where ``/etc/nv_tegra_release`` exists."""
|
|
96
|
+
return os.path.isfile(_TEGRA_RELEASE_PATH)
|
|
97
|
+
|
|
98
|
+
def collect(self) -> Dict[str, Any]:
|
|
99
|
+
"""Collect Jetson platform information.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
Dict[str, Any]
|
|
104
|
+
Keys include ``is_embedded_board``, ``board_name``,
|
|
105
|
+
``jetpack_version``, and -- when determinable --
|
|
106
|
+
``cuda_toolkit_version``.
|
|
107
|
+
"""
|
|
108
|
+
data: Dict[str, Any] = {
|
|
109
|
+
"is_embedded_board": True,
|
|
110
|
+
"architecture": Architecture.AARCH64,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
l4t_version = self._parse_l4t_version()
|
|
114
|
+
tegra_model = self._read_tegra_model()
|
|
115
|
+
|
|
116
|
+
board_name = "NVIDIA Jetson"
|
|
117
|
+
if tegra_model:
|
|
118
|
+
board_name = f"NVIDIA Jetson ({tegra_model})"
|
|
119
|
+
data["board_name"] = board_name
|
|
120
|
+
|
|
121
|
+
if l4t_version:
|
|
122
|
+
jetpack = L4T_TO_JETPACK.get(l4t_version)
|
|
123
|
+
if jetpack:
|
|
124
|
+
data["jetpack_version"] = jetpack
|
|
125
|
+
expected_cuda = JETPACK_CUDA.get(jetpack)
|
|
126
|
+
if expected_cuda:
|
|
127
|
+
data["cuda_toolkit_version"] = expected_cuda
|
|
128
|
+
|
|
129
|
+
data["gpu_available"] = True
|
|
130
|
+
|
|
131
|
+
return data
|
|
132
|
+
|
|
133
|
+
# -- Internal helpers ---------------------------------------------------
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _parse_l4t_version() -> Optional[str]:
|
|
137
|
+
"""Parse the L4T version string from ``/etc/nv_tegra_release``.
|
|
138
|
+
|
|
139
|
+
The first line typically looks like::
|
|
140
|
+
|
|
141
|
+
# R35 (release), REVISION: 4.1, ...
|
|
142
|
+
|
|
143
|
+
This method extracts ``"35.4.1"`` from that line.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
with open(_TEGRA_RELEASE_PATH, "r") as fh:
|
|
147
|
+
content = fh.read()
|
|
148
|
+
except (FileNotFoundError, PermissionError, OSError):
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
# Match "R<major> (release), REVISION: <minor>.<patch>"
|
|
152
|
+
match = re.search(
|
|
153
|
+
r"#\s*R(\d+)\s*\(release\),\s*REVISION:\s*(\d+\.\d+)",
|
|
154
|
+
content,
|
|
155
|
+
)
|
|
156
|
+
if match:
|
|
157
|
+
major = match.group(1)
|
|
158
|
+
minor_patch = match.group(2)
|
|
159
|
+
return f"{major}.{minor_patch}"
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _read_tegra_model() -> Optional[str]:
|
|
164
|
+
"""Read the Tegra chip ID and map it to a human-readable name.
|
|
165
|
+
|
|
166
|
+
The chip ID is read from
|
|
167
|
+
``/sys/module/tegra_fuse/parameters/tegra_chip_id``.
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
with open(_TEGRA_CHIP_ID_PATH, "r") as fh:
|
|
171
|
+
raw = fh.read().strip()
|
|
172
|
+
except (FileNotFoundError, PermissionError, OSError):
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
# Normalise to hex string
|
|
176
|
+
try:
|
|
177
|
+
chip_id = int(raw, 0)
|
|
178
|
+
hex_id = f"0x{chip_id:02x}"
|
|
179
|
+
except ValueError:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
return _TEGRA_CHIP_NAMES.get(hex_id)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# -- Rules ------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class JetPackCUDARule(Rule):
|
|
189
|
+
"""Verify that installed CUDA matches the expected JetPack CUDA version.
|
|
190
|
+
|
|
191
|
+
On a Jetson board, the JetPack release pins a specific CUDA toolkit
|
|
192
|
+
version. If the detected CUDA version does not match, libraries
|
|
193
|
+
compiled against the expected CUDA may crash or produce incorrect
|
|
194
|
+
results.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
rule_id = "JETPACK_CUDA_MISMATCH"
|
|
198
|
+
description = (
|
|
199
|
+
"Check that the installed CUDA version matches the expected "
|
|
200
|
+
"version for the detected JetPack release."
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def is_applicable(self, profile: SystemProfile) -> bool:
|
|
204
|
+
"""Only applies on Jetson boards with a known JetPack version."""
|
|
205
|
+
return (
|
|
206
|
+
profile.is_embedded_board
|
|
207
|
+
and profile.jetpack_version is not None
|
|
208
|
+
and profile.jetpack_version in JETPACK_CUDA
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def evaluate(
|
|
212
|
+
self,
|
|
213
|
+
profile: SystemProfile,
|
|
214
|
+
packages: List[PackageBinaryInfo],
|
|
215
|
+
) -> List[Finding]:
|
|
216
|
+
"""Compare detected CUDA with JetPack-expected CUDA."""
|
|
217
|
+
findings: List[Finding] = []
|
|
218
|
+
|
|
219
|
+
if profile.jetpack_version is None:
|
|
220
|
+
return findings
|
|
221
|
+
|
|
222
|
+
expected_cuda = JETPACK_CUDA.get(profile.jetpack_version)
|
|
223
|
+
if expected_cuda is None:
|
|
224
|
+
return findings
|
|
225
|
+
|
|
226
|
+
actual_cuda = profile.cuda_runtime_version or profile.cuda_toolkit_version
|
|
227
|
+
if actual_cuda is None:
|
|
228
|
+
findings.append(Finding(
|
|
229
|
+
rule_id=self.rule_id,
|
|
230
|
+
severity=Severity.WARNING,
|
|
231
|
+
title="CUDA not detected on Jetson board",
|
|
232
|
+
explanation=(
|
|
233
|
+
f"JetPack {profile.jetpack_version} expects CUDA "
|
|
234
|
+
f"{expected_cuda[0]}.{expected_cuda[1]}, but no CUDA "
|
|
235
|
+
f"installation was detected on this system."
|
|
236
|
+
),
|
|
237
|
+
suggestion=(
|
|
238
|
+
"Install the CUDA toolkit that matches your JetPack "
|
|
239
|
+
"version, or ensure that nvidia-smi / nvcc is accessible."
|
|
240
|
+
),
|
|
241
|
+
))
|
|
242
|
+
return findings
|
|
243
|
+
|
|
244
|
+
if actual_cuda[0] != expected_cuda[0] or actual_cuda[1] != expected_cuda[1]:
|
|
245
|
+
severity = Severity.CRITICAL if actual_cuda[0] != expected_cuda[0] else Severity.WARNING
|
|
246
|
+
findings.append(Finding(
|
|
247
|
+
rule_id=self.rule_id,
|
|
248
|
+
severity=severity,
|
|
249
|
+
title="CUDA version does not match JetPack expectation",
|
|
250
|
+
explanation=(
|
|
251
|
+
f"JetPack {profile.jetpack_version} expects CUDA "
|
|
252
|
+
f"{expected_cuda[0]}.{expected_cuda[1]}, but CUDA "
|
|
253
|
+
f"{actual_cuda[0]}.{actual_cuda[1]} was detected."
|
|
254
|
+
),
|
|
255
|
+
technical_detail=(
|
|
256
|
+
f"Expected CUDA {expected_cuda[0]}.{expected_cuda[1]} "
|
|
257
|
+
f"(JetPack {profile.jetpack_version}), "
|
|
258
|
+
f"found {actual_cuda[0]}.{actual_cuda[1]}."
|
|
259
|
+
),
|
|
260
|
+
suggestion=(
|
|
261
|
+
"Re-flash the Jetson with the correct JetPack SDK, or "
|
|
262
|
+
"install the matching CUDA toolkit version. Mixing CUDA "
|
|
263
|
+
"versions on Jetson is not recommended."
|
|
264
|
+
),
|
|
265
|
+
))
|
|
266
|
+
else:
|
|
267
|
+
findings.append(Finding(
|
|
268
|
+
rule_id=self.rule_id,
|
|
269
|
+
severity=Severity.PASSED,
|
|
270
|
+
title="CUDA version matches JetPack expectation",
|
|
271
|
+
explanation=(
|
|
272
|
+
f"CUDA {actual_cuda[0]}.{actual_cuda[1]} matches "
|
|
273
|
+
f"JetPack {profile.jetpack_version} requirements."
|
|
274
|
+
),
|
|
275
|
+
))
|
|
276
|
+
|
|
277
|
+
return findings
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class JetsonX86WheelRule(Rule):
|
|
281
|
+
"""Detect x86-only wheels installed on an ARM Jetson board.
|
|
282
|
+
|
|
283
|
+
Wheels built for ``manylinux_*_x86_64`` or ``linux_x86_64`` will
|
|
284
|
+
contain shared objects compiled for the wrong architecture and will
|
|
285
|
+
fail with ``ImportError`` or ``OSError`` when loaded on the Jetson's
|
|
286
|
+
AArch64 CPU.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
rule_id = "JETSON_X86_WHEEL"
|
|
290
|
+
description = (
|
|
291
|
+
"Detect x86-compiled wheels installed on an ARM Jetson board."
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
_X86_PLATFORM_FRAGMENTS = ("x86_64", "i686", "i386", "amd64")
|
|
295
|
+
|
|
296
|
+
def is_applicable(self, profile: SystemProfile) -> bool:
|
|
297
|
+
"""Only applies on Jetson boards (AArch64 embedded)."""
|
|
298
|
+
return (
|
|
299
|
+
profile.is_embedded_board
|
|
300
|
+
and profile.architecture in (Architecture.AARCH64, Architecture.ARMV7L)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def evaluate(
|
|
304
|
+
self,
|
|
305
|
+
profile: SystemProfile,
|
|
306
|
+
packages: List[PackageBinaryInfo],
|
|
307
|
+
) -> List[Finding]:
|
|
308
|
+
"""Check each package's wheel tags and shared objects for x86 targets."""
|
|
309
|
+
findings: List[Finding] = []
|
|
310
|
+
|
|
311
|
+
for pkg in packages:
|
|
312
|
+
if pkg.is_pure_python:
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
# Check wheel tags
|
|
316
|
+
for tag in pkg.wheel_tags:
|
|
317
|
+
platform_lower = tag.platform.lower()
|
|
318
|
+
if any(frag in platform_lower for frag in self._X86_PLATFORM_FRAGMENTS):
|
|
319
|
+
findings.append(Finding(
|
|
320
|
+
rule_id=self.rule_id,
|
|
321
|
+
severity=Severity.CRITICAL,
|
|
322
|
+
title=f"x86 wheel installed on ARM Jetson: {pkg.package_name}",
|
|
323
|
+
explanation=(
|
|
324
|
+
f"Package {pkg.package_name}=={pkg.package_version} "
|
|
325
|
+
f"was installed from an x86 wheel (platform tag: "
|
|
326
|
+
f"{tag.platform!r}), but this system is {profile.architecture.value}."
|
|
327
|
+
),
|
|
328
|
+
technical_detail=(
|
|
329
|
+
f"Wheel tag: {tag.interpreter}-{tag.abi}-{tag.platform}"
|
|
330
|
+
),
|
|
331
|
+
suggestion=(
|
|
332
|
+
f"Reinstall {pkg.package_name} with a wheel built "
|
|
333
|
+
f"for aarch64, or build from source: "
|
|
334
|
+
f"pip install --no-binary {pkg.package_name} {pkg.package_name}"
|
|
335
|
+
),
|
|
336
|
+
package=pkg.package_name,
|
|
337
|
+
package_version=pkg.package_version,
|
|
338
|
+
))
|
|
339
|
+
break # One finding per package is sufficient
|
|
340
|
+
|
|
341
|
+
# Also check shared objects for architecture mismatch
|
|
342
|
+
if not findings or findings[-1].package != pkg.package_name:
|
|
343
|
+
for so in pkg.shared_objects:
|
|
344
|
+
if so.architecture in (Architecture.X86_64, Architecture.I686):
|
|
345
|
+
findings.append(Finding(
|
|
346
|
+
rule_id=self.rule_id,
|
|
347
|
+
severity=Severity.CRITICAL,
|
|
348
|
+
title=f"x86 binary in package on ARM Jetson: {pkg.package_name}",
|
|
349
|
+
explanation=(
|
|
350
|
+
f"Shared object {so.filename} in "
|
|
351
|
+
f"{pkg.package_name}=={pkg.package_version} is "
|
|
352
|
+
f"compiled for {so.architecture.value}, but this "
|
|
353
|
+
f"Jetson runs {profile.architecture.value}."
|
|
354
|
+
),
|
|
355
|
+
technical_detail=f"Binary: {so.path}",
|
|
356
|
+
suggestion=(
|
|
357
|
+
f"Reinstall {pkg.package_name} from an ARM-compatible "
|
|
358
|
+
f"wheel or build from source."
|
|
359
|
+
),
|
|
360
|
+
package=pkg.package_name,
|
|
361
|
+
package_version=pkg.package_version,
|
|
362
|
+
))
|
|
363
|
+
break # One finding per package is sufficient
|
|
364
|
+
|
|
365
|
+
return findings
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# -- Plugin entry point -----------------------------------------------------
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def register(registry: HookRegistry) -> None:
|
|
372
|
+
"""Register Jetson extensions if running on a Tegra platform.
|
|
373
|
+
|
|
374
|
+
This function is called by the plugin loader. It only activates
|
|
375
|
+
when ``/etc/nv_tegra_release`` exists, indicating an L4T-based
|
|
376
|
+
Jetson system.
|
|
377
|
+
"""
|
|
378
|
+
if not os.path.exists(_TEGRA_RELEASE_PATH):
|
|
379
|
+
logger.debug("Jetson plugin: /etc/nv_tegra_release not found; not activating")
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
registry.add_probe(JetsonProbe())
|
|
383
|
+
registry.add_rule(JetPackCUDARule())
|
|
384
|
+
registry.add_rule(JetsonX86WheelRule())
|
|
385
|
+
logger.info("Jetson plugin activated")
|