Skip to content

Commit 398b545

Browse files
meakshdwozhurzhurz
authored
Several fixes for security issues
* Several fixes for security issues (bsc#1244561, CVE-2024-38822) (bsc#1244564, CVE-2024-38823) (bsc#1244565, CVE-2024-38824) (bsc#1244566, CVE-2024-38825) (bsc#1244567, CVE-2025-22240) (bsc#1244568, CVE-2025-22236) (bsc#1244570, CVE-2025-22241) (bsc#1244571, CVE-2025-22237) (bsc#1244572, CVE-2025-22238) (bsc#1244574, CVE-2025-22239) (bsc#1244575, CVE-2025-22242) Request server hardening - Each minion get's it's own aes session for request server communication. - Request client always includes id and token, these are always validated server side. - Add timestamp and enforce configurable ttl for request server messages. Other relevant commit messages: - Add deprecation message to salt.auth.pki - Add test and fix for file_recv cve - Prevent traversal in local_cache::save_minions - Fix traversal in gitfs find_file - Fix traversals in salt.utils.virt - Fix traversal in pub_ret - On-demand pillar fix - Include url validation tests - Minion event filtering - Reasonable failures when pillars timeout - Adjust and fix code and tests after backporting to openSUSE/release/3006.0 Co-authored-by: Pablo Suárez Hernández <[email protected]> * Fix test_pillar_timeout unit test in Salt Shaker * Fix tests failures on functional/channel/test_req_channel.py * Fix gitfs test failures due uncomplete cleanup * Fix cp.push module function and its integration test (#68053) fix file_recv path verification for subdirs Adapt backport to fit openSUSE/release/3006.0 * Make send_req_async wait longer (#68085) Allow send_req_async to wait longer when sending a return back to the master. The minion should wait at least as long as the max possible return timeout. Update creds when session key changes Add unit test to validate session key rotation Add changelog for #68079 * Remove token to prevent decoding errors (#68084) Clean up verify_load calls in master request server Remove tok in salt.channel.ReqServer.validate_token so it is not passed to the request handlers. Add tests around payload token removal Add changelog for #68076 * Fix checking of non-url style git remotes (#68089) Handle [email protected]/.. style remotes Fix checking of non-url style git remotes Fixes handling of git@hostname:/path/repo style remotes. Takes initial version from #68082 and fixes it. Still uses the shortcut of converting the remote to ssh:// URL style. Split out converting remote to URL Splits out converting remotes to URL form to allow testing of that conversion - without doing that risk issues with the regex Add additional test cases Add additional test cases based on gitfs docs and what they say should be valid Make utility functions classmethods Fix key vs remote wart Allow subdirs in GitFS find_file check (#68083) Add test for find_file in sub directories Add changelog for #68072 --------- Co-authored-by: Daniel A. Wozniak <[email protected]> Co-authored-by: hurzhurz <[email protected]>
1 parent a272ba8 commit 398b545

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2504
-558
lines changed

changelog/67941.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix cp.push module function and its integration test

changelog/68033.fixed.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
CVE-2024-38822
2+
Multiple methods in the salt master skip minion token validation. Therefore a misbehaving minion can impersonate another minion.
3+
4+
CVSS 2.7 V:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N
5+
6+
CVE-2024-38823
7+
Salt's request server is vulnerable to replay attacks when not using a TLS encrypted transport.
8+
9+
CVSS Score 2.7 AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N
10+
11+
CVE-2024-38824
12+
Directory traversal vulnerability in recv_file method allows arbitrary files to be written to the master cache directory.
13+
14+
CVSS Score 9.6 AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
15+
16+
CVE-2024-38825
17+
The salt.auth.pki module does not properly authenticate callers. The "password" field contains a public certificate which is validated against a CA certificate by the module. This is not pki authentication, as the caller does not need access to the corresponding private key for the authentication attempt to be accepted.
18+
19+
CVSS Score 6.4 AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
20+
21+
CVE-2025-22236
22+
Minion event bus authorization bypass. An attacker with access to a minion key can craft a message which may be able to execute a job on other minions (>= 3007.0).
23+
24+
CVSS 8.1 AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:L
25+
26+
CVE-2025-22237
27+
An attacker with access to a minion key can exploit the 'on demand' pillar functionality with a specially crafted git url which could cause and arbitrary command to be run on the master with the same privileges as the master process.
28+
29+
CVSS 6.7 AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
30+
31+
CVE-2025-22238
32+
Directory traversal attack in minion file cache creation. The master's default cache is vulnerable to a directory traversal attack. Which could be leveraged to write or overwrite 'cache' files outside of the cache directory.
33+
34+
CVSS 4.2 AV:L/AC:L/PR:H/UI:R/S:U/C:N/I:H/A:N
35+
36+
CVE-2025-22239
37+
Arbitrary event injection on Salt Master. The master's "_minion_event" method can be used by and authorized minion to send arbitrary events onto the master's event bus.
38+
39+
CVSS 8.1 AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:L
40+
41+
CVE-2025-22240
42+
Arbitrary directory creation or file deletion. In the find_file method of the GitFS class, a path is created using os.path.join using unvalidated input from the “tgt_env” variable. This can be exploited by an attacker to delete any file on the Master's process has permissions to
43+
44+
CVSS 6.3 AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H
45+
46+
CVE-2025-22241
47+
File contents overwrite the VirtKey class is called when “on-demand pillar” data is requested and uses un-validated input to create paths to the “pki directory”. The functionality is used to auto-accept Minion authentication keys based on a pre-placed “authorization file” at a specific location and is present in the default configuration.
48+
49+
CVSS 5.6 AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:N
50+
51+
CVE-2025-22242
52+
Worker process denial of service through file read operation. .A vulnerability exists in the Master's “pub_ret” method which is exposed to all minions. The un-sanitized input value “jid” is used to construct a path which is then opened for reading. An attacker could exploit this vulnerabilities by attempting to read from a filename that will not return any data, e.g. by targeting a pipe node on the proc file system.
53+
54+
CVSS 5.6 AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:N/A:H
55+
56+
This release also includes sqlite 3.50.1 to address CVE-2025-29087

changelog/68072.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix GitFS file_find for file in sub-directories

changelog/68076.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Token validation removes token from request handler payload

changelog/68079.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix minion connectivity issues by ensuring auth notices refreshed session token

changelog/68087.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix file_recv path verification to allow subdirs used by cp.push

salt/auth/pki.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import logging
1818

1919
import salt.utils.files
20+
import salt.utils.versions
2021

2122
# pylint: disable=import-error
2223
try:
@@ -30,7 +31,7 @@
3031
from Cryptodome.Util import asn1
3132
except ImportError:
3233
from Crypto.Util import asn1 # nosec
33-
import OpenSSL
34+
import OpenSSL # pylint: disable=W8410
3435
HAS_DEPS = True
3536
except ImportError:
3637
HAS_DEPS = False
@@ -71,6 +72,10 @@ def auth(username, password, **kwargs):
7172
your_user:
7273
- .*
7374
"""
75+
salt.utils.versions.warn_until(
76+
"Argon",
77+
"This module has been deprecated as it is known to be insecure.",
78+
)
7479
pem = password
7580
cacert_file = __salt__["config.get"]("external_auth:pki:ca_file")
7681

salt/channel/client.py

Lines changed: 97 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040

4141
log = logging.getLogger(__name__)
4242

43+
REQUEST_CHANNEL_TIMEOUT = 60
44+
REQUEST_CHANNEL_TRIES = 3
45+
4346

4447
class ReqChannel:
4548
"""
@@ -120,6 +123,9 @@ def factory(cls, opts, **kwargs):
120123
if io_loop is None:
121124
io_loop = salt.ext.tornado.ioloop.IOLoop.current()
122125

126+
timeout = opts.get("request_channel_timeout", REQUEST_CHANNEL_TIMEOUT)
127+
tries = opts.get("request_channel_tries", REQUEST_CHANNEL_TRIES)
128+
123129
crypt = kwargs.get("crypt", "aes")
124130
if crypt != "clear":
125131
# we don't need to worry about auth as a kwarg, since its a singleton
@@ -128,16 +134,26 @@ def factory(cls, opts, **kwargs):
128134
auth = None
129135

130136
transport = salt.transport.request_client(opts, io_loop)
131-
return cls(opts, transport, auth)
137+
return cls(opts, transport, auth, tries=tries, timeout=timeout)
132138

133-
def __init__(self, opts, transport, auth, **kwargs):
139+
def __init__(
140+
self,
141+
opts,
142+
transport,
143+
auth,
144+
timeout=REQUEST_CHANNEL_TIMEOUT,
145+
tries=REQUEST_CHANNEL_TRIES,
146+
**kwargs
147+
):
134148
self.opts = dict(opts)
135149
self.transport = transport
136150
self.auth = auth
137151
self.master_pubkey_path = None
138152
if self.auth:
139153
self.master_pubkey_path = os.path.join(self.opts["pki_dir"], self.auth.mpub)
140154
self._closing = False
155+
self.timeout = timeout
156+
self.tries = tries
141157

142158
@property
143159
def crypt(self):
@@ -149,35 +165,90 @@ def crypt(self):
149165
def ttype(self):
150166
return self.transport.ttype
151167

152-
def _package_load(self, load):
153-
return {
168+
def _package_load(self, load, nonce=None):
169+
"""
170+
Prepare the load to be sent over the wire.
171+
172+
For aes channels add a nonce, timestamp and signed token to the load
173+
before encrypting it using our aes session key. Then wrap the encrypted
174+
load with some meta data. For 'clear' encryption, no extra feilds are
175+
added to the load. The unencyrpted load is wrapped with meta data.
176+
"""
177+
if self.crypt == "aes":
178+
if nonce is None:
179+
nonce = uuid.uuid4().hex
180+
try:
181+
load["nonce"] = nonce
182+
load["ts"] = int(time.time())
183+
load["tok"] = self.auth.gen_token(b"salt")
184+
load["id"] = self.opts["id"]
185+
except TypeError:
186+
# Backwards compatability for non dict loads, let the load get
187+
# sent and fail to authenticate.
188+
log.warning(
189+
"Invalid load passed to request channel. Type is %s should be dict.",
190+
type(load),
191+
)
192+
193+
load = self.auth.session_crypticle.dumps(load)
194+
195+
ret = {
154196
"enc": self.crypt,
155197
"load": load,
156-
"version": 2,
198+
"version": 3,
157199
}
200+
if self.crypt == "aes":
201+
ret["id"] = self.opts["id"]
202+
return ret
203+
204+
@salt.ext.tornado.gen.coroutine
205+
def _send_with_retry(self, load, tries, timeout):
206+
_try = 1
207+
while True:
208+
try:
209+
ret = yield self.transport.send(
210+
load,
211+
timeout=timeout,
212+
)
213+
break
214+
except Exception as exc: # pylint: disable=broad-except
215+
log.trace("Failed to send msg %r", exc)
216+
if _try >= tries:
217+
raise
218+
else:
219+
_try += 1
220+
continue
221+
raise salt.ext.tornado.gen.Return(ret)
158222

159223
@salt.ext.tornado.gen.coroutine
160224
def crypted_transfer_decode_dictentry(
161225
self,
162226
load,
163227
dictkey=None,
164-
timeout=60,
228+
timeout=None,
229+
tries=None,
165230
):
166-
nonce = uuid.uuid4().hex
167-
load["nonce"] = nonce
231+
if timeout is None:
232+
timeout = self.timeout
233+
if tries is None:
234+
tries = self.tries
168235
if not self.auth.authenticated:
169236
yield self.auth.authenticate()
170-
ret = yield self.transport.send(
171-
self._package_load(self.auth.crypticle.dumps(load)),
172-
timeout=timeout,
237+
238+
nonce = uuid.uuid4().hex
239+
ret = yield self._send_with_retry(
240+
self._package_load(load, nonce),
241+
tries,
242+
timeout,
173243
)
174244
key = self.auth.get_keys()
175245
if "key" not in ret:
176246
# Reauth in the case our key is deleted on the master side.
177247
yield self.auth.authenticate()
178-
ret = yield self.transport.send(
179-
self._package_load(self.auth.crypticle.dumps(load)),
180-
timeout=timeout,
248+
ret = yield self._send_with_retry(
249+
self._package_load(load, nonce),
250+
tries,
251+
timeout,
181252
)
182253
if HAS_M2:
183254
aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding)
@@ -209,7 +280,7 @@ def verify_signature(self, data, sig):
209280
return salt.crypt.verify_signature(self.master_pubkey_path, data, sig)
210281

211282
@salt.ext.tornado.gen.coroutine
212-
def _crypted_transfer(self, load, timeout=60, raw=False):
283+
def _crypted_transfer(self, load, timeout, raw=False):
213284
"""
214285
Send a load across the wire, with encryption
215286
@@ -222,25 +293,24 @@ def _crypted_transfer(self, load, timeout=60, raw=False):
222293
:param dict load: A load to send across the wire
223294
:param int timeout: The number of seconds on a response before failing
224295
"""
225-
nonce = uuid.uuid4().hex
226-
if load and isinstance(load, dict):
227-
load["nonce"] = nonce
228296

229297
@salt.ext.tornado.gen.coroutine
230298
def _do_transfer():
231299
# Yield control to the caller. When send() completes, resume by populating data with the Future.result
300+
nonce = uuid.uuid4().hex
232301
data = yield self.transport.send(
233-
self._package_load(self.auth.crypticle.dumps(load)),
302+
self._package_load(load, nonce),
234303
timeout=timeout,
235304
)
236305
# we may not have always data
237306
# as for example for saltcall ret submission, this is a blind
238307
# communication, we do not subscribe to return events, we just
239308
# upload the results to the master
240309
if data:
241-
data = self.auth.crypticle.loads(data, raw, nonce=nonce)
310+
data = self.auth.session_crypticle.loads(data, raw, nonce=nonce)
242311
if not raw or self.ttype == "tcp": # XXX Why is this needed for tcp
243312
data = salt.transport.frame.decode_embedded_strs(data)
313+
244314
raise salt.ext.tornado.gen.Return(data)
245315

246316
if not self.auth.authenticated:
@@ -256,7 +326,7 @@ def _do_transfer():
256326
raise salt.ext.tornado.gen.Return(ret)
257327

258328
@salt.ext.tornado.gen.coroutine
259-
def _uncrypted_transfer(self, load, timeout=60):
329+
def _uncrypted_transfer(self, load, timeout):
260330
"""
261331
Send a load across the wire in cleartext
262332
@@ -271,14 +341,18 @@ def _uncrypted_transfer(self, load, timeout=60):
271341
raise salt.ext.tornado.gen.Return(ret)
272342

273343
@salt.ext.tornado.gen.coroutine
274-
def send(self, load, tries=3, timeout=60, raw=False):
344+
def send(self, load, tries=None, timeout=None, raw=False):
275345
"""
276346
Send a request, return a future which will complete when we send the message
277347
278348
:param dict load: A load to send across the wire
279349
:param int tries: The number of times to make before failure
280350
:param int timeout: The number of seconds on a response before failing
281351
"""
352+
if timeout is None:
353+
timeout = self.timeout
354+
if tries is None:
355+
tries = self.tries
282356
_try = 1
283357
while True:
284358
try:
@@ -427,7 +501,7 @@ def _package_load(self, load):
427501
return {
428502
"enc": self.crypt,
429503
"load": load,
430-
"version": 2,
504+
"version": 3,
431505
}
432506

433507
@salt.ext.tornado.gen.coroutine

0 commit comments

Comments
 (0)