Skip to content

Commit a843e21

Browse files
committed
added /sys{} path restrictions
1 parent d371ae4 commit a843e21

2 files changed

Lines changed: 78 additions & 6 deletions

File tree

nodescraper/plugins/inband/sys_settings/sys_settings_collector.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,32 @@ def _paths_from_args(args: Optional[SysSettingsCollectorArgs]) -> list[str]:
5656
"""Extract list of sysfs paths from collection args.
5757
5858
Args:
59-
args: Collector args containing paths to read, or None.
59+
args: Collector args containing paths to read, or None. May be a dict.
6060
6161
Returns:
6262
List of sysfs paths; empty if args is None or args.paths is empty.
6363
"""
6464
if args is None:
6565
return []
66-
return list(args.paths) if args.paths else []
66+
paths = args.get("paths") if isinstance(args, dict) else getattr(args, "paths", None)
67+
return list(paths) if paths else []
68+
69+
70+
def _path_under_sys(path: str) -> Optional[str]:
71+
"""Normalize path to the suffix under /sys/ for use in 'cat /sys/{}'."""
72+
if ".." in path:
73+
return None
74+
p = path.strip().lstrip("/")
75+
if p.startswith("sys/"):
76+
p = p[4:]
77+
if p.startswith("/"):
78+
return None
79+
return p if p else None
80+
81+
82+
def _sysfs_full_path(suffix: str) -> str:
83+
"""Return full path /sys/{suffix}."""
84+
return f"/sys/{suffix}"
6785

6886

6987
class SysSettingsCollector(InBandDataCollector[SysSettingsDataModel, SysSettingsCollectorArgs]):
@@ -72,7 +90,7 @@ class SysSettingsCollector(InBandDataCollector[SysSettingsDataModel, SysSettings
7290
DATA_MODEL = SysSettingsDataModel
7391
SUPPORTED_OS_FAMILY: set[OSFamily] = {OSFamily.LINUX}
7492

75-
CMD = "cat {}"
93+
CMD = "cat /sys/{}"
7694

7795
def collect_data(
7896
self, args: Optional[SysSettingsCollectorArgs] = None
@@ -102,14 +120,25 @@ def collect_data(
102120

103121
readings: dict[str, str] = {}
104122
for path in paths:
105-
res = self._run_sut_cmd(self.CMD.format(path), sudo=False)
123+
suffix = _path_under_sys(path)
124+
if suffix is None:
125+
self._log_event(
126+
category=EventCategory.OS,
127+
description=f"Skipping path not under /sys or invalid: {path!r}",
128+
data={"path": path},
129+
priority=EventPriority.WARNING,
130+
console_log=True,
131+
)
132+
continue
133+
full_path = _sysfs_full_path(suffix)
134+
res = self._run_sut_cmd(self.CMD.format(suffix), sudo=False)
106135
if res.exit_code == 0 and res.stdout:
107136
value = _parse_bracketed_setting(res.stdout) or res.stdout.strip()
108-
readings[path] = value
137+
readings[full_path] = value
109138
else:
110139
self._log_event(
111140
category=EventCategory.OS,
112-
description=f"Failed to read sysfs path: {path}",
141+
description=f"Failed to read sysfs path: {full_path}",
113142
data={"exit_code": res.exit_code},
114143
priority=EventPriority.WARNING,
115144
console_log=True,

test/unit/plugin/test_sys_settings_collector.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,46 @@ def test_collector_raises_on_non_linux(system_info, conn_mock):
120120
system_info.os_family = OSFamily.WINDOWS
121121
with pytest.raises(SystemCompatibilityError, match="not supported"):
122122
SysSettingsCollector(system_info=system_info, connection=conn_mock)
123+
124+
125+
def test_collect_data_uses_sys_only_command(linux_sys_settings_collector):
126+
seen_commands = []
127+
128+
def run_cmd(cmd, **kwargs):
129+
seen_commands.append(cmd)
130+
return make_artifact(0, "value")
131+
132+
linux_sys_settings_collector._run_sut_cmd = run_cmd
133+
args = {"paths": [PATH_ENABLED]}
134+
result, data = linux_sys_settings_collector.collect_data(args)
135+
136+
assert result.status == ExecutionStatus.OK
137+
assert len(seen_commands) == 1
138+
assert seen_commands[0].startswith("cat /sys/")
139+
assert data.readings.get(PATH_ENABLED) == "value"
140+
141+
142+
def test_collect_data_skips_path_with_dotdot(linux_sys_settings_collector):
143+
seen_commands = []
144+
145+
def run_cmd(cmd, **kwargs):
146+
seen_commands.append(cmd)
147+
return make_artifact(0, "safe")
148+
149+
linux_sys_settings_collector._run_sut_cmd = run_cmd
150+
args = {
151+
"paths": [
152+
"/sys/kernel/mm/transparent_hugepage/enabled",
153+
"/sys/../etc/passwd",
154+
"/sys/something/../../etc",
155+
"sys/kernel/mm/transparent_hugepage/defrag",
156+
]
157+
}
158+
result, data = linux_sys_settings_collector.collect_data(args)
159+
160+
assert result.status == ExecutionStatus.OK
161+
assert len(seen_commands) == 2
162+
assert all(c.startswith("cat /sys/") for c in seen_commands)
163+
assert data.readings.get("/sys/kernel/mm/transparent_hugepage/enabled") == "safe"
164+
assert data.readings.get("/sys/kernel/mm/transparent_hugepage/defrag") == "safe"
165+
assert "/etc" not in str(seen_commands)

0 commit comments

Comments
 (0)