encrypt: fix bcrypt salt string formatting (#87184)

* fix bcrypt salt string formatting on musl libc by ensuring it is always zero-padded to 2 digits

Fixes: #87180

Signed-off-by: Abhijeet Kasurde <Akasurde@redhat.com>
This commit is contained in:
Abhijeet Kasurde
2026-06-30 10:08:31 -04:00
committed by GitHub
parent 305ead5130
commit 78c337b827
5 changed files with 22 additions and 12 deletions
@@ -0,0 +1,2 @@
bugfixes:
- encrypt - fix bcrypt salt string formatting on musl libc by ensuring it is always zero-padded to 2 digits (https://github.com/ansible/ansible/issues/87180).
+3 -7
View File
@@ -12,7 +12,7 @@ from dataclasses import dataclass
__all__ = ['CryptFacade']
_FAILURE_TOKENS = frozenset({b'*0', b'*1'})
_FAILURE_TOKENS = frozenset({b'*', b'*0', b'*1'})
@dataclass(frozen=True)
@@ -93,10 +93,9 @@ class CryptFacade:
if self._use_crypt_r:
self._crypt_impl.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(self._CryptData)]
self._crypt_impl.restype = ctypes.c_char_p
else:
self._crypt_impl.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
self._crypt_impl.restype = ctypes.c_char_p
self._crypt_impl.restype = ctypes.c_char_p
# Try to load crypt_gensalt (available in libxcrypt)
try:
@@ -133,10 +132,7 @@ class CryptFacade:
error_msg = os.strerror(errno)
raise OSError(errno, f'crypt failed: {error_msg}')
if result is None:
raise ValueError('crypt failed: invalid salt or unsupported algorithm')
if result in _FAILURE_TOKENS:
if result is None or result in _FAILURE_TOKENS:
raise ValueError('crypt failed: invalid salt or unsupported algorithm')
return result
+2 -1
View File
@@ -181,7 +181,8 @@ class CryptHash(BaseHash):
saltstring = f'${ident}' if ident else ''
if rounds:
if self.algo_data.rounds_format == 'cost':
saltstring += f'${rounds}'
# musl libc requires a 2-digit bcrypt cost ($2b$05$, not $2b$5$).
saltstring += f'${rounds:02d}'
else:
saltstring += f'$rounds={rounds}'
saltstring += f'${salt}'
@@ -82,7 +82,7 @@ class TestCryptFacade:
def test_crypt_fail_errno(self, mocker: MockerFixture) -> None:
"""Test crypt() setting failure errno raises OSError."""
mocker.patch('ctypes.get_errno', return_value=errno.EBADFD)
mocker.patch('ctypes.get_errno', return_value=errno.EBADF)
crypt_facade = CryptFacade()
with pytest.raises(OSError, match=r'crypt failed:'):
@@ -96,10 +96,11 @@ class TestCryptFacade:
with pytest.raises(ValueError, match=r'crypt failed: invalid salt or unsupported algorithm'):
crypt_facade.crypt(b"test", b"123")
def test_crypt_result_failure(self, mocker: MockerFixture) -> None:
@pytest.mark.parametrize('failure_token', sorted(list(_FAILURE_TOKENS)))
def test_crypt_result_failure(self, mocker: MockerFixture, failure_token: bytes) -> None:
"""Test crypt() implementation returning failure token raises ValueError."""
crypt_facade = CryptFacade()
mocker.patch.object(crypt_facade, '_crypt_impl', return_value=list(_FAILURE_TOKENS)[0])
mocker.patch.object(crypt_facade, '_crypt_impl', return_value=failure_token)
with pytest.raises(ValueError, match=r'crypt failed: invalid salt or unsupported algorithm'):
crypt_facade.crypt(b"test", b"123")
@@ -124,7 +125,7 @@ class TestCryptFacade:
def test_crypt_gensalt_fail_errno(self, mocker: MockerFixture) -> None:
"""Test crypt_gensalt() setting failure errno raises OSError."""
mocker.patch('ctypes.get_errno', return_value=errno.EBADFD)
mocker.patch('ctypes.get_errno', return_value=errno.EBADF)
crypt_facade = CryptFacade()
with pytest.raises(OSError, match=r'crypt_gensalt failed:'):
+10
View File
@@ -288,6 +288,16 @@ class TestCryptHash:
with pytest.raises(AnsibleError, match=r"crypt does not support 'sha256_crypt' algorithm"):
crypt_hash.hash("123", salt="12345678")
def test_build_saltstring_bcrypt_cost_padding(self, mocker: MockerFixture) -> None:
"""musl libc requires a 2-digit bcrypt cost in the salt string."""
mocker.patch('ansible.utils.encrypt.HAS_CRYPT', True)
mocker.patch('ansible._internal._encryption._crypt.CryptFacade.has_crypt_gensalt', False)
crypt_hash = encrypt.CryptHash("bcrypt")
saltstring = crypt_hash._build_saltstring('2b', 5, '1234567890123456789012', None)
assert saltstring == '$2b$05$1234567890123456789012'
class TestPasslibHash:
"""