Skip to content

Commit 90c24ee

Browse files
committed
new release (2.24.1)
Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
1 parent 35eed3c commit 90c24ee

8 files changed

Lines changed: 68 additions & 17 deletions

File tree

com.redhat.tuned.policy

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
<defaults>
4444
<allow_any>auth_admin</allow_any>
4545
<allow_inactive>auth_admin</allow_inactive>
46-
<allow_active>yes</allow_active>
46+
<allow_active>auth_admin</allow_active>
4747
</defaults>
4848
</action>
4949

@@ -103,7 +103,7 @@
103103
<defaults>
104104
<allow_any>auth_admin</allow_any>
105105
<allow_inactive>auth_admin</allow_inactive>
106-
<allow_active>yes</allow_active>
106+
<allow_active>auth_admin</allow_active>
107107
</defaults>
108108
</action>
109109

@@ -113,7 +113,7 @@
113113
<defaults>
114114
<allow_any>auth_admin</allow_any>
115115
<allow_inactive>auth_admin</allow_inactive>
116-
<allow_active>yes</allow_active>
116+
<allow_active>auth_admin</allow_active>
117117
</defaults>
118118
</action>
119119

@@ -123,7 +123,7 @@
123123
<defaults>
124124
<allow_any>auth_admin</allow_any>
125125
<allow_inactive>auth_admin</allow_inactive>
126-
<allow_active>yes</allow_active>
126+
<allow_active>auth_admin</allow_active>
127127
</defaults>
128128
</action>
129129

@@ -223,7 +223,7 @@
223223
<defaults>
224224
<allow_any>auth_admin</allow_any>
225225
<allow_inactive>auth_admin</allow_inactive>
226-
<allow_active>yes</allow_active>
226+
<allow_active>auth_admin</allow_active>
227227
</defaults>
228228
</action>
229229

@@ -253,7 +253,7 @@
253253
<defaults>
254254
<allow_any>auth_admin</allow_any>
255255
<allow_inactive>auth_admin</allow_inactive>
256-
<allow_active>yes</allow_active>
256+
<allow_active>auth_admin</allow_active>
257257
</defaults>
258258
</action>
259259

@@ -263,7 +263,7 @@
263263
<defaults>
264264
<allow_any>auth_admin</allow_any>
265265
<allow_inactive>auth_admin</allow_inactive>
266-
<allow_active>yes</allow_active>
266+
<allow_active>auth_admin</allow_active>
267267
</defaults>
268268
</action>
269269

tuned.spec

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
Summary: A dynamic adaptive system tuning daemon
6161
Name: tuned
62-
Version: 2.24.0
62+
Version: 2.24.1
6363
Release: 1%{?prerel1}%{?with_snapshot:.%{git_suffix}}%{?dist}
6464
License: GPL-2.0-or-later AND CC-BY-SA-3.0
6565
Source0: https://github.com/redhat-performance/%{name}/archive/v%{version}%{?prerel2}/%{name}-%{version}%{?prerel2}.tar.gz
@@ -631,6 +631,14 @@ fi
631631
%config(noreplace) %{_sysconfdir}/tuned/ppd.conf
632632

633633
%changelog
634+
* Tue Nov 26 2024 Jaroslav Škarvada <jskarvad@redhat.com> - 2.24.1-1
635+
- new release
636+
- fixed privileged execution of arbitrary scripts by active local user
637+
resolves: CVE-2024-52336
638+
- added sanity checks for API methods parameters
639+
resolves: CVE-2024-52337
640+
- tuned-ppd: fixed controller init to correctly set _on_battery
641+
634642
* Wed Aug 7 2024 Jaroslav Škarvada <jskarvad@redhat.com> - 2.24.0-1
635643
- new release
636644
- clear plugin repository when stopping tuning

tuned/consts.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import logging
2+
import string
3+
4+
NAMES_ALLOWED_CHARS = string.ascii_letters + string.digits + " !@'+-.,/:;_$&*()%<=>?#[]{|}^~" + '"'
5+
NAMES_MAX_LENGTH = 4096
26

37
GLOBAL_CONFIG_FILE = "/etc/tuned/tuned-main.conf"
48
ACTIVE_PROFILE_FILE = "/etc/tuned/active_profile"

tuned/daemon/controller.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ def _switch_profile(self, profile_name, manual):
189189
def switch_profile(self, profile_name, caller = None):
190190
if caller == "":
191191
return (False, "Unauthorized")
192+
if not self._cmd.is_valid_name(profile_name):
193+
return (False, "Invalid profile_name")
192194
return self._switch_profile(profile_name, True)
193195

194196
@exports.export("", "(bs)")
@@ -262,8 +264,8 @@ def profiles2(self, caller = None):
262264

263265
@exports.export("s", "(bsss)")
264266
def profile_info(self, profile_name, caller = None):
265-
if caller == "":
266-
return tuple(False, "", "", "")
267+
if caller == "" or not self._cmd.is_valid_name(profile_name):
268+
return (False, "", "", "")
267269
if profile_name is None or profile_name == "":
268270
profile_name = self.active_profile()
269271
return tuple(self._daemon.profile_loader.profile_locator.get_profile_attrs(profile_name, [consts.PROFILE_ATTR_SUMMARY, consts.PROFILE_ATTR_DESCRIPTION], [""]))
@@ -294,7 +296,7 @@ def get_all_plugins(self, caller = None):
294296
dictionary -- {plugin_name: {parameter_name: default_value}}
295297
"""
296298
if caller == "":
297-
return False
299+
return {}
298300
plugins = {}
299301
for plugin_class in self._daemon.get_all_plugins():
300302
plugin_name = plugin_class.__module__.split(".")[-1].split("_", 1)[1]
@@ -307,8 +309,8 @@ def get_all_plugins(self, caller = None):
307309
@exports.export("s","s")
308310
def get_plugin_documentation(self, plugin_name, caller = None):
309311
"""Return docstring of plugin's class"""
310-
if caller == "":
311-
return False
312+
if caller == "" or not self._cmd.is_valid_name(plugin_name):
313+
return ""
312314
return self._daemon.get_plugin_documentation(str(plugin_name))
313315

314316
@exports.export("s","a{ss}")
@@ -321,8 +323,8 @@ def get_plugin_hints(self, plugin_name, caller = None):
321323
Return:
322324
dictionary -- {parameter_name: hint}
323325
"""
324-
if caller == "":
325-
return False
326+
if caller == "" or not self._cmd.is_valid_name(plugin_name):
327+
return {}
326328
return self._daemon.get_plugin_hints(str(plugin_name))
327329

328330
@exports.export("s", "b")
@@ -335,7 +337,7 @@ def register_socket_signal_path(self, path, caller = None):
335337
Return:
336338
bool -- True on success
337339
"""
338-
if caller == "":
340+
if caller == "" or not self._cmd.is_valid_name(path):
339341
return False
340342
if self._daemon._application and self._daemon._application._unix_socket_exporter:
341343
self._daemon._application._unix_socket_exporter.register_signal_path(path)
@@ -349,6 +351,10 @@ def register_socket_signal_path(self, path, caller = None):
349351
def instance_acquire_devices(self, devices, instance_name, caller = None):
350352
if caller == "":
351353
return (False, "Unauthorized")
354+
if not self._cmd.is_valid_name(devices):
355+
return (False, "Invalid devices")
356+
if not self._cmd.is_valid_name(instance_name):
357+
return (False, "Invalid instance_name")
352358
found = False
353359
for instance_target in self._daemon._unit_manager.instances:
354360
if instance_target.name == instance_name:
@@ -399,6 +405,8 @@ def get_instances(self, plugin_name, caller = None):
399405
"""
400406
if caller == "":
401407
return (False, "Unauthorized", [])
408+
if not self._cmd.is_valid_name(plugin_name):
409+
return (False, "Invalid plugin_name", [])
402410
if plugin_name != "" and plugin_name not in self.get_all_plugins().keys():
403411
rets = "Plugin '%s' does not exist" % plugin_name
404412
log.error(rets)
@@ -422,6 +430,8 @@ def instance_get_devices(self, instance_name, caller = None):
422430
"""
423431
if caller == "":
424432
return (False, "Unauthorized", [])
433+
if not self._cmd.is_valid_name(instance_name):
434+
return (False, "Invalid instance_name", [])
425435
for instance in self._daemon._unit_manager.instances:
426436
if instance.name == instance_name:
427437
return (True, "OK", sorted(list(instance.processed_devices)))
@@ -444,6 +454,13 @@ def instance_create(self, plugin_name, instance_name, options, caller = None):
444454
"""
445455
if caller == "":
446456
return (False, "Unauthorized")
457+
if not self._cmd.is_valid_name(plugin_name):
458+
return (False, "Invalid plugin_name")
459+
if not self._cmd.is_valid_name(instance_name):
460+
return (False, "Invalid instance_name")
461+
for (key, value) in options.items():
462+
if not self._cmd.is_valid_name(key) or not self._cmd.is_valid_name(value):
463+
return (False, "Invalid options")
447464
plugins = {p.name: p for p in self._daemon._unit_manager.plugins}
448465
if not plugin_name in plugins.keys():
449466
rets = "Plugin '%s' not found" % plugin_name
@@ -499,6 +516,8 @@ def instance_destroy(self, instance_name, caller = None):
499516
"""
500517
if caller == "":
501518
return (False, "Unauthorized")
519+
if not self._cmd.is_valid_name(instance_name):
520+
return (False, "Invalid instance_name")
502521
try:
503522
instance = [i for i in self._daemon._unit_manager.instances if i.name == instance_name][0]
504523
except IndexError:

tuned/plugins/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,14 @@ def _instance_pre_static(self, instance, enabling):
213213
def _instance_post_static(self, instance, enabling):
214214
pass
215215

216+
def _safe_script_path(self, path):
217+
path = os.path.realpath(path)
218+
profile_paths = self._global_cfg.get_list(consts.CFG_PROFILE_DIRS, consts.CFG_DEF_PROFILE_DIRS)
219+
for p in profile_paths:
220+
if path.startswith(p):
221+
return True
222+
return False
223+
216224
def _call_device_script(self, instance, script, op, devices, rollback = consts.ROLLBACK_SOFT):
217225
if script is None:
218226
return None
@@ -223,6 +231,10 @@ def _call_device_script(self, instance, script, op, devices, rollback = consts.R
223231
log.error("Relative paths cannot be used in script_pre or script_post. " \
224232
+ "Use ${i:PROFILE_DIR}.")
225233
return False
234+
if not self._safe_script_path(script):
235+
log.error("Paths outside of the profile directories cannot be used in the " \
236+
+ "script_pre or script_post, ignoring script: '%s'" % script)
237+
return False
226238
dir_name = os.path.dirname(script)
227239
ret = True
228240
for dev in devices:

tuned/plugins/plugin_script.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ def _call_scripts(self, scripts, arguments):
7575
for script in scripts:
7676
environ = os.environ
7777
environ.update(self._variables.get_env())
78+
if not self._safe_script_path(script):
79+
log.error("Paths outside of the profile directories cannot be used in the script, " \
80+
+ "ignoring script: '%s'." % script)
81+
continue
7882
log.info("calling script '%s' with arguments '%s'" % (script, str(arguments)))
7983
log.debug("using environment '%s'" % str(list(environ.items())))
8084
try:

tuned/utils/commands.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,3 +548,7 @@ def tr(self, text, source_chars, dest_chars):
548548
import string
549549
trans = string.maketrans(source_chars, dest_chars)
550550
return text.translate(trans)
551+
552+
# Checks if name contains only valid characters and has valid length or is empty string or None
553+
def is_valid_name(self, name):
554+
return not name or (all(c in consts.NAMES_ALLOWED_CHARS for c in name) and len(name) <= consts.NAMES_MAX_LENGTH)

tuned/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
TUNED_VERSION_MAJOR = 2
22
TUNED_VERSION_MINOR = 24
3-
TUNED_VERSION_PATCH = 0
3+
TUNED_VERSION_PATCH = 1
44

55
TUNED_VERSION_STR = "%d.%d.%d" % (TUNED_VERSION_MAJOR, TUNED_VERSION_MINOR, TUNED_VERSION_PATCH)

0 commit comments

Comments
 (0)