creds: decode Base64 when reading encrypted credentials

Encrypted credentials produced by systemd-creds are stored as Base64.
Decode them before decrypting in read_credential_with_decryption(), while
leaving plaintext credentials unchanged and retaining their precedence.

Bound the decoded encrypted input and report oversized credentials
explicitly. Match the plaintext reader's string semantics by rejecting
embedded NUL bytes when no size output is requested, while preserving
binary payloads for callers that request their size.

Add regression coverage for missing and malformed credentials, plaintext
precedence, decoded size boundaries, single-line and wrapped Base64,
empty strings, and binary data. Keep test outputs and host secrets scoped
to private fixtures.

Fixes #43621
This commit is contained in:
Totoro
2026-09-10 00:20:09 +09:00
committed by Yu Watanabe
parent 6a68191199
commit 951cfae10b
2 changed files with 225 additions and 8 deletions
+18 -8
View File
@@ -180,7 +180,7 @@ int read_credential(const char *name, void **ret, size_t *ret_size) {
}
int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size) {
_cleanup_(iovec_done_erase) struct iovec ret_iovec = {};
_cleanup_(iovec_done_erase) struct iovec decrypted = {};
_cleanup_(erase_and_freep) void *data = NULL;
_cleanup_free_ char *fn = NULL;
size_t sz = 0;
@@ -218,16 +218,20 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si
if (!fn)
return log_oom();
/* Encrypted credentials are stored as Base64, unlike the plaintext credentials checked above. */
r = read_full_file_full(
AT_FDCWD, fn,
UINT64_MAX, SIZE_MAX,
READ_FULL_FILE_SECURE,
UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX,
READ_FULL_FILE_SECURE|READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER,
NULL,
(char**) &data, &sz);
if (r == -ENOENT)
goto not_found;
if (r == -E2BIG)
return log_error_errno(r, "Encrypted credential '%s' exceeds the size limit.", name);
if (r < 0)
return log_error_errno(r, "Failed to read encrypted credential data: %m");
return log_error_errno(
r, "Failed to read or Base64-decode encrypted credential '%s': %m", name);
if (geteuid() != 0)
r = ipc_decrypt_credential(
@@ -236,7 +240,7 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si
getuid(),
&IOVEC_MAKE(data, sz),
CREDENTIAL_ANY_SCOPE,
&ret_iovec);
&decrypted);
else
r = decrypt_credential_and_warn(
name,
@@ -246,14 +250,20 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si
getuid(),
&IOVEC_MAKE(data, sz),
CREDENTIAL_ANY_SCOPE,
&ret_iovec);
&decrypted);
if (r < 0)
return r;
/* Match read_credential(): without a size output, the caller expects a string. */
if (!ret_size && decrypted.iov_len > 0 && memchr(decrypted.iov_base, 0, decrypted.iov_len))
return log_error_errno(
SYNTHETIC_ERRNO(EBADMSG),
"Decrypted credential '%s' contains embedded NUL bytes, refusing.", name);
if (ret)
*ret = TAKE_PTR(ret_iovec.iov_base);
*ret = TAKE_PTR(decrypted.iov_base);
if (ret_size)
*ret_size = ret_iovec.iov_len;
*ret_size = decrypted.iov_len;
return 1; /* found */