mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-17 00:13:45 +00:00

Fix a leak reported by kmemleak:
unreferenced object 0xffff8880093bf7a0 (size 32):
comm "swapper/0", pid 1, jiffies 4294877529
hex dump (first 32 bytes):
9d 18 86 16 f6 38 52 fe 86 91 5b b8 40 b4 a8 86 .....8R...[.@...
ff 3e 6b b0 f8 19 b4 9b 89 33 93 d3 93 85 42 95 .>k......3....B.
backtrace (crc 8ba12f3b):
kmemleak_alloc+0x8d/0xa0
__kmalloc_noprof+0x3cd/0x4d0
prep_buf+0x36/0x70
load_buf+0x10d/0x1c0
krb5_test_one_prf+0x1e1/0x3c0
krb5_selftest.cold+0x7c/0x54c
crypto_krb5_init+0xd/0x20
do_one_initcall+0xa5/0x230
do_initcalls+0x213/0x250
kernel_init_freeable+0x220/0x260
kernel_init+0x1d/0x170
ret_from_fork+0x301/0x410
ret_from_fork_asm+0x1a/0x30
Fixes: fc0cf10c04
("crypto/krb5: Implement crypto self-testing")
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
Acked-by: David Howells <dhowells@redhat.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
546 lines
12 KiB
C
546 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* Kerberos library self-testing
|
|
*
|
|
* Copyright (C) 2025 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/slab.h>
|
|
#include <crypto/skcipher.h>
|
|
#include <crypto/hash.h>
|
|
#include "internal.h"
|
|
|
|
#define VALID(X) \
|
|
({ \
|
|
bool __x = (X); \
|
|
if (__x) { \
|
|
pr_warn("!!! TESTINVAL %s:%u\n", __FILE__, __LINE__); \
|
|
ret = -EBADMSG; \
|
|
} \
|
|
__x; \
|
|
})
|
|
|
|
#define CHECK(X) \
|
|
({ \
|
|
bool __x = (X); \
|
|
if (__x) { \
|
|
pr_warn("!!! TESTFAIL %s:%u\n", __FILE__, __LINE__); \
|
|
ret = -EBADMSG; \
|
|
} \
|
|
__x; \
|
|
})
|
|
|
|
enum which_key {
|
|
TEST_KC, TEST_KE, TEST_KI,
|
|
};
|
|
|
|
#if 0
|
|
static void dump_sg(struct scatterlist *sg, unsigned int limit)
|
|
{
|
|
unsigned int index = 0, n = 0;
|
|
|
|
for (; sg && limit > 0; sg = sg_next(sg)) {
|
|
unsigned int off = sg->offset, len = umin(sg->length, limit);
|
|
const void *p = kmap_local_page(sg_page(sg));
|
|
|
|
limit -= len;
|
|
while (len > 0) {
|
|
unsigned int part = umin(len, 32);
|
|
|
|
pr_notice("[%x] %04x: %*phN\n", n, index, part, p + off);
|
|
index += part;
|
|
off += part;
|
|
len -= part;
|
|
}
|
|
|
|
kunmap_local(p);
|
|
n++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int prep_buf(struct krb5_buffer *buf)
|
|
{
|
|
buf->data = kmalloc(buf->len, GFP_KERNEL);
|
|
if (!buf->data)
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
#define PREP_BUF(BUF, LEN) \
|
|
do { \
|
|
(BUF)->len = (LEN); \
|
|
ret = prep_buf((BUF)); \
|
|
if (ret < 0) \
|
|
goto out; \
|
|
} while (0)
|
|
|
|
static int load_buf(struct krb5_buffer *buf, const char *from)
|
|
{
|
|
size_t len = strlen(from);
|
|
int ret;
|
|
|
|
if (len > 1 && from[0] == '\'') {
|
|
PREP_BUF(buf, len - 1);
|
|
memcpy(buf->data, from + 1, len - 1);
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (VALID(len & 1))
|
|
return -EINVAL;
|
|
|
|
PREP_BUF(buf, len / 2);
|
|
ret = hex2bin(buf->data, from, buf->len);
|
|
if (ret < 0) {
|
|
VALID(1);
|
|
goto out;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
#define LOAD_BUF(BUF, FROM) do { ret = load_buf(BUF, FROM); if (ret < 0) goto out; } while (0)
|
|
|
|
static void clear_buf(struct krb5_buffer *buf)
|
|
{
|
|
kfree(buf->data);
|
|
buf->len = 0;
|
|
buf->data = NULL;
|
|
}
|
|
|
|
/*
|
|
* Perform a pseudo-random function check.
|
|
*/
|
|
static int krb5_test_one_prf(const struct krb5_prf_test *test)
|
|
{
|
|
const struct krb5_enctype *krb5 = crypto_krb5_find_enctype(test->etype);
|
|
struct krb5_buffer key = {}, octet = {}, result = {}, prf = {};
|
|
int ret;
|
|
|
|
if (!krb5)
|
|
return -EOPNOTSUPP;
|
|
|
|
pr_notice("Running %s %s\n", krb5->name, test->name);
|
|
|
|
LOAD_BUF(&key, test->key);
|
|
LOAD_BUF(&octet, test->octet);
|
|
LOAD_BUF(&prf, test->prf);
|
|
PREP_BUF(&result, krb5->prf_len);
|
|
|
|
if (VALID(result.len != prf.len)) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
ret = krb5->profile->calc_PRF(krb5, &key, &octet, &result, GFP_KERNEL);
|
|
if (ret < 0) {
|
|
CHECK(1);
|
|
pr_warn("PRF calculation failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (memcmp(result.data, prf.data, result.len) != 0) {
|
|
CHECK(1);
|
|
ret = -EKEYREJECTED;
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
clear_buf(&result);
|
|
clear_buf(&prf);
|
|
clear_buf(&octet);
|
|
clear_buf(&key);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Perform a key derivation check.
|
|
*/
|
|
static int krb5_test_key(const struct krb5_enctype *krb5,
|
|
const struct krb5_buffer *base_key,
|
|
const struct krb5_key_test_one *test,
|
|
enum which_key which)
|
|
{
|
|
struct krb5_buffer key = {}, result = {};
|
|
int ret;
|
|
|
|
LOAD_BUF(&key, test->key);
|
|
PREP_BUF(&result, key.len);
|
|
|
|
switch (which) {
|
|
case TEST_KC:
|
|
ret = krb5_derive_Kc(krb5, base_key, test->use, &result, GFP_KERNEL);
|
|
break;
|
|
case TEST_KE:
|
|
ret = krb5_derive_Ke(krb5, base_key, test->use, &result, GFP_KERNEL);
|
|
break;
|
|
case TEST_KI:
|
|
ret = krb5_derive_Ki(krb5, base_key, test->use, &result, GFP_KERNEL);
|
|
break;
|
|
default:
|
|
VALID(1);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
CHECK(1);
|
|
pr_warn("Key derivation failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (memcmp(result.data, key.data, result.len) != 0) {
|
|
CHECK(1);
|
|
ret = -EKEYREJECTED;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
clear_buf(&key);
|
|
clear_buf(&result);
|
|
return ret;
|
|
}
|
|
|
|
static int krb5_test_one_key(const struct krb5_key_test *test)
|
|
{
|
|
const struct krb5_enctype *krb5 = crypto_krb5_find_enctype(test->etype);
|
|
struct krb5_buffer base_key = {};
|
|
int ret;
|
|
|
|
if (!krb5)
|
|
return -EOPNOTSUPP;
|
|
|
|
pr_notice("Running %s %s\n", krb5->name, test->name);
|
|
|
|
LOAD_BUF(&base_key, test->key);
|
|
|
|
ret = krb5_test_key(krb5, &base_key, &test->Kc, TEST_KC);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = krb5_test_key(krb5, &base_key, &test->Ke, TEST_KE);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = krb5_test_key(krb5, &base_key, &test->Ki, TEST_KI);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
out:
|
|
clear_buf(&base_key);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Perform an encryption test.
|
|
*/
|
|
static int krb5_test_one_enc(const struct krb5_enc_test *test, void *buf)
|
|
{
|
|
const struct krb5_enctype *krb5 = crypto_krb5_find_enctype(test->etype);
|
|
struct crypto_aead *ci = NULL;
|
|
struct krb5_buffer K0 = {}, Ke = {}, Ki = {}, keys = {};
|
|
struct krb5_buffer conf = {}, plain = {}, ct = {};
|
|
struct scatterlist sg[1];
|
|
size_t data_len, data_offset, message_len;
|
|
int ret;
|
|
|
|
if (!krb5)
|
|
return -EOPNOTSUPP;
|
|
|
|
pr_notice("Running %s %s\n", krb5->name, test->name);
|
|
|
|
/* Load the test data into binary buffers. */
|
|
LOAD_BUF(&conf, test->conf);
|
|
LOAD_BUF(&plain, test->plain);
|
|
LOAD_BUF(&ct, test->ct);
|
|
|
|
if (test->K0) {
|
|
LOAD_BUF(&K0, test->K0);
|
|
} else {
|
|
LOAD_BUF(&Ke, test->Ke);
|
|
LOAD_BUF(&Ki, test->Ki);
|
|
|
|
ret = krb5->profile->load_encrypt_keys(krb5, &Ke, &Ki, &keys, GFP_KERNEL);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
|
|
if (VALID(conf.len != krb5->conf_len) ||
|
|
VALID(ct.len != krb5->conf_len + plain.len + krb5->cksum_len))
|
|
goto out;
|
|
|
|
data_len = plain.len;
|
|
message_len = crypto_krb5_how_much_buffer(krb5, KRB5_ENCRYPT_MODE,
|
|
data_len, &data_offset);
|
|
|
|
if (CHECK(message_len != ct.len)) {
|
|
pr_warn("Encrypted length mismatch %zu != %u\n", message_len, ct.len);
|
|
goto out;
|
|
}
|
|
if (CHECK(data_offset != conf.len)) {
|
|
pr_warn("Data offset mismatch %zu != %u\n", data_offset, conf.len);
|
|
goto out;
|
|
}
|
|
|
|
memcpy(buf, conf.data, conf.len);
|
|
memcpy(buf + data_offset, plain.data, plain.len);
|
|
|
|
/* Allocate a crypto object and set its key. */
|
|
if (test->K0)
|
|
ci = crypto_krb5_prepare_encryption(krb5, &K0, test->usage, GFP_KERNEL);
|
|
else
|
|
ci = krb5_prepare_encryption(krb5, &keys, GFP_KERNEL);
|
|
|
|
if (IS_ERR(ci)) {
|
|
ret = PTR_ERR(ci);
|
|
ci = NULL;
|
|
pr_err("Couldn't alloc AEAD %s: %d\n", krb5->encrypt_name, ret);
|
|
goto out;
|
|
}
|
|
|
|
/* Encrypt the message. */
|
|
sg_init_one(sg, buf, message_len);
|
|
ret = crypto_krb5_encrypt(krb5, ci, sg, 1, message_len,
|
|
data_offset, data_len, true);
|
|
if (ret < 0) {
|
|
CHECK(1);
|
|
pr_warn("Encryption failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
if (ret != message_len) {
|
|
CHECK(1);
|
|
pr_warn("Encrypted message wrong size %x != %zx\n", ret, message_len);
|
|
goto out;
|
|
}
|
|
|
|
if (memcmp(buf, ct.data, ct.len) != 0) {
|
|
CHECK(1);
|
|
pr_warn("Ciphertext mismatch\n");
|
|
pr_warn("BUF %*phN\n", ct.len, buf);
|
|
pr_warn("CT %*phN\n", ct.len, ct.data);
|
|
pr_warn("PT %*phN%*phN\n", conf.len, conf.data, plain.len, plain.data);
|
|
ret = -EKEYREJECTED;
|
|
goto out;
|
|
}
|
|
|
|
/* Decrypt the encrypted message. */
|
|
data_offset = 0;
|
|
data_len = message_len;
|
|
ret = crypto_krb5_decrypt(krb5, ci, sg, 1, &data_offset, &data_len);
|
|
if (ret < 0) {
|
|
CHECK(1);
|
|
pr_warn("Decryption failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (CHECK(data_offset != conf.len) ||
|
|
CHECK(data_len != plain.len))
|
|
goto out;
|
|
|
|
if (memcmp(buf, conf.data, conf.len) != 0) {
|
|
CHECK(1);
|
|
pr_warn("Confounder mismatch\n");
|
|
pr_warn("ENC %*phN\n", conf.len, buf);
|
|
pr_warn("DEC %*phN\n", conf.len, conf.data);
|
|
ret = -EKEYREJECTED;
|
|
goto out;
|
|
}
|
|
|
|
if (memcmp(buf + conf.len, plain.data, plain.len) != 0) {
|
|
CHECK(1);
|
|
pr_warn("Plaintext mismatch\n");
|
|
pr_warn("BUF %*phN\n", plain.len, buf + conf.len);
|
|
pr_warn("PT %*phN\n", plain.len, plain.data);
|
|
ret = -EKEYREJECTED;
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
clear_buf(&ct);
|
|
clear_buf(&plain);
|
|
clear_buf(&conf);
|
|
clear_buf(&keys);
|
|
clear_buf(&Ki);
|
|
clear_buf(&Ke);
|
|
clear_buf(&K0);
|
|
if (ci)
|
|
crypto_free_aead(ci);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Perform a checksum test.
|
|
*/
|
|
static int krb5_test_one_mic(const struct krb5_mic_test *test, void *buf)
|
|
{
|
|
const struct krb5_enctype *krb5 = crypto_krb5_find_enctype(test->etype);
|
|
struct crypto_shash *ci = NULL;
|
|
struct scatterlist sg[1];
|
|
struct krb5_buffer K0 = {}, Kc = {}, keys = {}, plain = {}, mic = {};
|
|
size_t offset, len, message_len;
|
|
int ret;
|
|
|
|
if (!krb5)
|
|
return -EOPNOTSUPP;
|
|
|
|
pr_notice("Running %s %s\n", krb5->name, test->name);
|
|
|
|
/* Allocate a crypto object and set its key. */
|
|
if (test->K0) {
|
|
LOAD_BUF(&K0, test->K0);
|
|
ci = crypto_krb5_prepare_checksum(krb5, &K0, test->usage, GFP_KERNEL);
|
|
} else {
|
|
LOAD_BUF(&Kc, test->Kc);
|
|
|
|
ret = krb5->profile->load_checksum_key(krb5, &Kc, &keys, GFP_KERNEL);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ci = krb5_prepare_checksum(krb5, &Kc, GFP_KERNEL);
|
|
}
|
|
if (IS_ERR(ci)) {
|
|
ret = PTR_ERR(ci);
|
|
ci = NULL;
|
|
pr_err("Couldn't alloc shash %s: %d\n", krb5->cksum_name, ret);
|
|
goto out;
|
|
}
|
|
|
|
/* Load the test data into binary buffers. */
|
|
LOAD_BUF(&plain, test->plain);
|
|
LOAD_BUF(&mic, test->mic);
|
|
|
|
len = plain.len;
|
|
message_len = crypto_krb5_how_much_buffer(krb5, KRB5_CHECKSUM_MODE,
|
|
len, &offset);
|
|
|
|
if (CHECK(message_len != mic.len + plain.len)) {
|
|
pr_warn("MIC length mismatch %zu != %u\n",
|
|
message_len, mic.len + plain.len);
|
|
goto out;
|
|
}
|
|
|
|
memcpy(buf + offset, plain.data, plain.len);
|
|
|
|
/* Generate a MIC generation request. */
|
|
sg_init_one(sg, buf, 1024);
|
|
|
|
ret = crypto_krb5_get_mic(krb5, ci, NULL, sg, 1, 1024,
|
|
krb5->cksum_len, plain.len);
|
|
if (ret < 0) {
|
|
CHECK(1);
|
|
pr_warn("Get MIC failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
len = ret;
|
|
|
|
if (CHECK(len != plain.len + mic.len)) {
|
|
pr_warn("MIC length mismatch %zu != %u\n", len, plain.len + mic.len);
|
|
goto out;
|
|
}
|
|
|
|
if (memcmp(buf, mic.data, mic.len) != 0) {
|
|
CHECK(1);
|
|
pr_warn("MIC mismatch\n");
|
|
pr_warn("BUF %*phN\n", mic.len, buf);
|
|
pr_warn("MIC %*phN\n", mic.len, mic.data);
|
|
ret = -EKEYREJECTED;
|
|
goto out;
|
|
}
|
|
|
|
/* Generate a verification request. */
|
|
offset = 0;
|
|
ret = crypto_krb5_verify_mic(krb5, ci, NULL, sg, 1, &offset, &len);
|
|
if (ret < 0) {
|
|
CHECK(1);
|
|
pr_warn("Verify MIC failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (CHECK(offset != mic.len) ||
|
|
CHECK(len != plain.len))
|
|
goto out;
|
|
|
|
if (memcmp(buf + offset, plain.data, plain.len) != 0) {
|
|
CHECK(1);
|
|
pr_warn("Plaintext mismatch\n");
|
|
pr_warn("BUF %*phN\n", plain.len, buf + offset);
|
|
pr_warn("PT %*phN\n", plain.len, plain.data);
|
|
ret = -EKEYREJECTED;
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
clear_buf(&mic);
|
|
clear_buf(&plain);
|
|
clear_buf(&keys);
|
|
clear_buf(&K0);
|
|
clear_buf(&Kc);
|
|
if (ci)
|
|
crypto_free_shash(ci);
|
|
return ret;
|
|
}
|
|
|
|
int krb5_selftest(void)
|
|
{
|
|
void *buf;
|
|
int ret = 0, i;
|
|
|
|
buf = kmalloc(4096, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
pr_notice("\n");
|
|
pr_notice("Running selftests\n");
|
|
|
|
for (i = 0; krb5_prf_tests[i].name; i++) {
|
|
ret = krb5_test_one_prf(&krb5_prf_tests[i]);
|
|
if (ret < 0) {
|
|
if (ret != -EOPNOTSUPP)
|
|
goto out;
|
|
pr_notice("Skipping %s\n", krb5_prf_tests[i].name);
|
|
}
|
|
}
|
|
|
|
for (i = 0; krb5_key_tests[i].name; i++) {
|
|
ret = krb5_test_one_key(&krb5_key_tests[i]);
|
|
if (ret < 0) {
|
|
if (ret != -EOPNOTSUPP)
|
|
goto out;
|
|
pr_notice("Skipping %s\n", krb5_key_tests[i].name);
|
|
}
|
|
}
|
|
|
|
for (i = 0; krb5_enc_tests[i].name; i++) {
|
|
memset(buf, 0x5a, 4096);
|
|
ret = krb5_test_one_enc(&krb5_enc_tests[i], buf);
|
|
if (ret < 0) {
|
|
if (ret != -EOPNOTSUPP)
|
|
goto out;
|
|
pr_notice("Skipping %s\n", krb5_enc_tests[i].name);
|
|
}
|
|
}
|
|
|
|
for (i = 0; krb5_mic_tests[i].name; i++) {
|
|
memset(buf, 0x5a, 4096);
|
|
ret = krb5_test_one_mic(&krb5_mic_tests[i], buf);
|
|
if (ret < 0) {
|
|
if (ret != -EOPNOTSUPP)
|
|
goto out;
|
|
pr_notice("Skipping %s\n", krb5_mic_tests[i].name);
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
out:
|
|
pr_notice("Selftests %s\n", ret == 0 ? "succeeded" : "failed");
|
|
kfree(buf);
|
|
return ret;
|
|
}
|