mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
A deeply nested or pathologically large KMIP node tree crafted by a malicious server can wrap the length calculation around to a small value. Detect this and return an error in this case. Assisted-by: IBM Bob:2.0.0 Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com> Reviewed-by: Finn Callies <fcallies@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
534 lines
12 KiB
C
534 lines
12 KiB
C
/*
|
|
* libkmipclient - KMIP client library
|
|
*
|
|
* Copyright IBM Corp. 2021
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <endian.h>
|
|
#include <string.h>
|
|
|
|
#include "kmip.h"
|
|
#include "utils.h"
|
|
|
|
#define KMIP_TTLV_HEADER_LENGTH 8
|
|
#define KMIP_TTLV_BLOCK_LENGTH 8
|
|
|
|
/**
|
|
* Decode a KMIP node from the data in BIO using the TTLV encoding.
|
|
*
|
|
* @param bio the OpenSSL bio to read the data from
|
|
* @param size Optional: If not NULL:
|
|
* On entry: The number of bytes available to read
|
|
* On return: decremented by the number of bytes read
|
|
* If NULL, it is assumed that we can read from bio
|
|
* as many bytes as needed.
|
|
* @param node On return: the decoded node. The newly allocated
|
|
* node has a reference count of 1.
|
|
* @param max_nesting_level the maximum nesting levels of structures within the
|
|
* KMIP node. If the nesting level is reached, E2BIG
|
|
* is returned.
|
|
* @param debug if true, debug messages are printed
|
|
*
|
|
* @returns 0 in case of success, or a negative errno value
|
|
*/
|
|
int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
|
|
size_t max_nesting_level, bool debug)
|
|
{
|
|
unsigned char padding[KMIP_TTLV_BLOCK_LENGTH];
|
|
unsigned char ttlv[KMIP_TTLV_HEADER_LENGTH];
|
|
size_t value_len, pad_len;
|
|
struct kmip_node *n, *e;
|
|
void *value = NULL;
|
|
uint32_t int32;
|
|
uint64_t int64;
|
|
int rc;
|
|
|
|
if (max_nesting_level == 0)
|
|
return -E2BIG;
|
|
|
|
if (bio == NULL || node == NULL)
|
|
return -EINVAL;
|
|
|
|
if (size != NULL)
|
|
kmip_debug(debug, "size: %lu", *size);
|
|
else
|
|
kmip_debug(debug, "size: unknown");
|
|
|
|
if (size != NULL && *size < sizeof(ttlv)) {
|
|
kmip_debug(debug, "length %u > available size %lu",
|
|
sizeof(ttlv), *size);
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
if (BIO_read(bio, ttlv, sizeof(ttlv)) != sizeof(ttlv)) {
|
|
kmip_debug(debug, "BIO_read failed");
|
|
return -EIO;
|
|
}
|
|
if (size != NULL)
|
|
*size -= sizeof(ttlv);
|
|
|
|
n = calloc(1, sizeof(struct kmip_node));
|
|
if (n == NULL) {
|
|
kmip_debug(debug, "calloc failed");
|
|
return -ENOMEM;
|
|
}
|
|
n->ref_count = 1;
|
|
|
|
/* Tag: 3-byte binary unsigned integer, transmitted big endian */
|
|
n->tag |= (uint32_t)ttlv[0] << 16;
|
|
n->tag |= (uint32_t)ttlv[1] << 8;
|
|
n->tag |= (uint32_t)ttlv[2];
|
|
|
|
/* Type: 1 byte containing a coded value that indicates the data type */
|
|
n->type = ttlv[3];
|
|
|
|
/* Length: 32-bit binary integer, transmitted big-endian */
|
|
n->length |= (uint32_t)ttlv[4] << 24;
|
|
n->length |= (uint32_t)ttlv[5] << 16;
|
|
n->length |= (uint32_t)ttlv[6] << 8;
|
|
n->length |= (uint32_t)ttlv[7];
|
|
|
|
kmip_debug(debug, "tag: 0x%x type: 0x%x, length: %u", n->tag, n->type,
|
|
n->length);
|
|
|
|
switch (n->type) {
|
|
case KMIP_TYPE_STRUCTURE:
|
|
value_len = n->length;
|
|
break;
|
|
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
value_len = n->length;
|
|
if (value_len > INT_MAX) {
|
|
rc = -EMSGSIZE;
|
|
goto out;
|
|
}
|
|
|
|
value = calloc(1, value_len + 1);
|
|
if (value == NULL) {
|
|
kmip_debug(debug, "calloc failed");
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_INTEGER:
|
|
case KMIP_TYPE_ENUMERATION:
|
|
case KMIP_TYPE_INTERVAL:
|
|
value_len = sizeof(int32);
|
|
value = &int32;
|
|
break;
|
|
|
|
case KMIP_TYPE_LONG_INTEGER:
|
|
case KMIP_TYPE_BOOLEAN:
|
|
case KMIP_TYPE_DATE_TIME:
|
|
case KMIP_TYPE_DATE_TIME_EXTENDED:
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
default:
|
|
kmip_debug(debug, "unknown type: 0x%x", n->type);
|
|
rc = -EBADMSG;
|
|
goto out;
|
|
}
|
|
|
|
if (n->length != value_len) {
|
|
kmip_debug(debug, "length %u not as expected (%lu)", n->length,
|
|
value_len);
|
|
rc = -EBADMSG;
|
|
goto out;
|
|
}
|
|
if (size != NULL && *size < n->length) {
|
|
kmip_debug(debug, "length %u > available size %lu", n->length,
|
|
*size);
|
|
rc = -EMSGSIZE;
|
|
goto out;
|
|
}
|
|
|
|
if (n->type != KMIP_TYPE_STRUCTURE && value_len > 0) {
|
|
if (BIO_read(bio, value, value_len) != (int)value_len) {
|
|
kmip_debug(debug, "BIO_read failed");
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
if (size != NULL)
|
|
*size -= value_len;
|
|
|
|
if ((value_len % KMIP_TTLV_BLOCK_LENGTH) != 0) {
|
|
pad_len = KMIP_TTLV_BLOCK_LENGTH -
|
|
(value_len % KMIP_TTLV_BLOCK_LENGTH);
|
|
|
|
kmip_debug(debug, "pad_len: %lu", pad_len);
|
|
if (BIO_read(bio, padding, pad_len) != (int)pad_len) {
|
|
kmip_debug(debug, "BIO_read failed (padding)");
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
if (size != NULL) {
|
|
if (*size < pad_len) {
|
|
rc = -EMSGSIZE;
|
|
goto out;
|
|
}
|
|
|
|
*size -= pad_len;
|
|
}
|
|
}
|
|
|
|
switch (n->type) {
|
|
case KMIP_TYPE_STRUCTURE:
|
|
while (value_len > 0) {
|
|
rc = kmip_decode_ttlv(bio, &value_len, &e,
|
|
max_nesting_level - 1, debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_decode_ttlv failed: "
|
|
"rc: %d", rc);
|
|
goto out;
|
|
}
|
|
rc = kmip_node_add_structure_element(n, e);
|
|
kmip_node_free(e);
|
|
if (rc != 0) {
|
|
kmip_debug(debug,
|
|
"kmip_node_structure_add_element "
|
|
"failed: rc: %d", rc);
|
|
goto out;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_INTEGER:
|
|
n->integer_value = be32toh(int32);
|
|
break;
|
|
|
|
case KMIP_TYPE_LONG_INTEGER:
|
|
n->long_value = be64toh(int64);
|
|
break;
|
|
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
rc = kmip_decode_bignum(value, value_len,
|
|
&n->big_integer_value);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_decode_bignum failed");
|
|
goto out;
|
|
}
|
|
free(value);
|
|
value = NULL;
|
|
break;
|
|
|
|
case KMIP_TYPE_ENUMERATION:
|
|
n->enumeration_value = be32toh(int32);
|
|
break;
|
|
|
|
case KMIP_TYPE_BOOLEAN:
|
|
n->boolean_value = int64 != 0;
|
|
break;
|
|
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
n->text_value = value;
|
|
break;
|
|
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
n->bytes_value = value;
|
|
break;
|
|
|
|
case KMIP_TYPE_DATE_TIME:
|
|
n->date_time_value = be64toh(int64);
|
|
break;
|
|
|
|
case KMIP_TYPE_INTERVAL:
|
|
n->interval_value = be32toh(int32);
|
|
break;
|
|
|
|
case KMIP_TYPE_DATE_TIME_EXTENDED:
|
|
n->date_time_ext_value = be64toh(int64);
|
|
break;
|
|
|
|
default:
|
|
kmip_debug(debug, "unknown type: 0x%x", n->type);
|
|
rc = -EBADMSG;
|
|
goto out;
|
|
}
|
|
|
|
*node = n;
|
|
rc = 0;
|
|
|
|
out:
|
|
if (rc != 0) {
|
|
switch (n->type) {
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
free(value);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
kmip_node_free(n);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Gets the length of the value part of a KMIP node (in TTLV encoding)
|
|
*/
|
|
static int kmip_node_get_length(struct kmip_node *node, size_t *length)
|
|
{
|
|
struct kmip_node *element;
|
|
size_t len, prev_len;
|
|
int rc;
|
|
|
|
if (node == NULL || length == NULL)
|
|
return -EINVAL;
|
|
|
|
switch (node->type) {
|
|
case KMIP_TYPE_STRUCTURE:
|
|
*length = 0;
|
|
element = node->structure_value;
|
|
while (element != NULL) {
|
|
rc = kmip_node_get_length(element, &len);
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
prev_len = *length;
|
|
*length += KMIP_TTLV_HEADER_LENGTH + len;
|
|
if ((len % KMIP_TTLV_BLOCK_LENGTH) != 0)
|
|
*length += KMIP_TTLV_BLOCK_LENGTH -
|
|
(len % KMIP_TTLV_BLOCK_LENGTH);
|
|
if (*length < prev_len)
|
|
return -EOVERFLOW;
|
|
|
|
element = element->next;
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_INTEGER:
|
|
case KMIP_TYPE_ENUMERATION:
|
|
case KMIP_TYPE_INTERVAL:
|
|
*length = sizeof(int32_t);
|
|
break;
|
|
|
|
case KMIP_TYPE_LONG_INTEGER:
|
|
case KMIP_TYPE_BOOLEAN:
|
|
case KMIP_TYPE_DATE_TIME:
|
|
case KMIP_TYPE_DATE_TIME_EXTENDED:
|
|
*length = sizeof(int64_t);
|
|
break;
|
|
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
*length = kmip_encode_bignum_length(node->big_integer_value);
|
|
/* BIG INTEGERS must be a multiple of 8 bytes long */
|
|
if ((*length % KMIP_BIG_INTEGER_BLOCK_LENGTH) != 0)
|
|
*length += KMIP_BIG_INTEGER_BLOCK_LENGTH -
|
|
(*length % KMIP_BIG_INTEGER_BLOCK_LENGTH);
|
|
break;
|
|
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
*length = node->length;
|
|
break;
|
|
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
if (node->text_value != NULL)
|
|
*length = strlen(node->text_value);
|
|
else
|
|
*length = 0;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Encode a KMIP node into a BIO using the TTLV encoding.
|
|
*
|
|
* @param node the node to encode
|
|
* @param bio the OpenSSL bio to write the data to
|
|
* @param size On return: the number of bytes written to BIO
|
|
* @param debug if true, debug messages are printed
|
|
*
|
|
* @returns 0 in case of success, or a negative errno value
|
|
*/
|
|
int kmip_encode_ttlv(struct kmip_node *node, BIO *bio, size_t *size,
|
|
bool debug)
|
|
{
|
|
const unsigned char padding[KMIP_TTLV_BLOCK_LENGTH] = { 0 };
|
|
unsigned char ttlv[KMIP_TTLV_HEADER_LENGTH];
|
|
size_t len, elem_len, value_len, pad_len;
|
|
struct kmip_node *element;
|
|
void *value = NULL;
|
|
uint32_t int32;
|
|
uint64_t int64;
|
|
int rc;
|
|
|
|
if (bio == NULL || node == NULL || size == NULL)
|
|
return -EINVAL;
|
|
|
|
kmip_debug(debug, "tag: 0x%x type: 0x%x, length: %u", node->tag,
|
|
node->type, node->length);
|
|
|
|
*size = 0;
|
|
|
|
/* Update node's length field to match node's current data */
|
|
rc = kmip_node_get_length(node, &len);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_node_get_length failed");
|
|
return rc;
|
|
}
|
|
node->length = len;
|
|
|
|
/* Tag: 3-byte binary unsigned integer, transmitted big endian */
|
|
ttlv[0] = (node->tag & 0xff0000) >> 16;
|
|
ttlv[1] = (node->tag & 0xff00) >> 8;
|
|
ttlv[2] = (node->tag & 0xff);
|
|
|
|
/* Type: 1 byte containing a coded value that indicates the data type */
|
|
ttlv[3] = node->type;
|
|
|
|
/* Length: 32-bit binary integer, transmitted big-endian */
|
|
ttlv[4] = (node->length & 0xff000000) >> 24;
|
|
ttlv[5] = (node->length & 0xff0000) >> 16;
|
|
ttlv[6] = (node->length & 0xff00) >> 8;
|
|
ttlv[7] = (node->length & 0xff);
|
|
|
|
if (BIO_write(bio, ttlv, sizeof(ttlv)) != sizeof(ttlv)) {
|
|
kmip_debug(debug, "BIO_write failed");
|
|
return -EIO;
|
|
}
|
|
*size += sizeof(ttlv);
|
|
|
|
switch (node->type) {
|
|
case KMIP_TYPE_STRUCTURE:
|
|
value_len = 0;
|
|
element = node->structure_value;
|
|
while (element != NULL) {
|
|
rc = kmip_encode_ttlv(element, bio, &elem_len, debug);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_encode_ttlv failed");
|
|
return rc;
|
|
}
|
|
value_len += elem_len;
|
|
element = element->next;
|
|
}
|
|
if (value_len != node->length) {
|
|
kmip_debug(debug, "written length %lu not as expected "
|
|
"(%u)", len, node->length);
|
|
return -EIO;
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_INTEGER:
|
|
int32 = htobe32(node->integer_value);
|
|
value_len = sizeof(int32);
|
|
value = &int32;
|
|
break;
|
|
|
|
case KMIP_TYPE_LONG_INTEGER:
|
|
int64 = htobe64(node->long_value);
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
case KMIP_TYPE_BIG_INTEGER:
|
|
value_len = node->length; /* was already calculated above */
|
|
value = malloc(value_len);
|
|
if (value == NULL) {
|
|
kmip_debug(debug, "malloc failed");
|
|
return -ENOMEM;
|
|
}
|
|
rc = kmip_encode_bignum(node->big_integer_value, value,
|
|
value_len);
|
|
if (rc != 0) {
|
|
kmip_debug(debug, "kmip_encode_bignum failed");
|
|
goto out;
|
|
}
|
|
break;
|
|
|
|
case KMIP_TYPE_ENUMERATION:
|
|
int32 = htobe32(node->enumeration_value);
|
|
value_len = sizeof(int32);
|
|
value = &int32;
|
|
break;
|
|
|
|
case KMIP_TYPE_BOOLEAN:
|
|
int64 = node->boolean_value ? 1 : 0;
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
case KMIP_TYPE_TEXT_STRING:
|
|
value_len = node->length;
|
|
value = node->text_value;
|
|
break;
|
|
|
|
case KMIP_TYPE_BYTE_STRING:
|
|
value_len = node->length;
|
|
value = node->bytes_value;
|
|
break;
|
|
|
|
case KMIP_TYPE_DATE_TIME:
|
|
int64 = htobe64(node->date_time_value);
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
case KMIP_TYPE_INTERVAL:
|
|
int32 = htobe32(node->interval_value);
|
|
value_len = sizeof(int32);
|
|
value = &int32;
|
|
break;
|
|
|
|
case KMIP_TYPE_DATE_TIME_EXTENDED:
|
|
int64 = htobe64(node->date_time_ext_value);
|
|
value_len = sizeof(int64);
|
|
value = &int64;
|
|
break;
|
|
|
|
default:
|
|
kmip_debug(debug, "unknown type: 0x%x", node->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (value != NULL) {
|
|
if (BIO_write(bio, value, value_len) != (int)value_len) {
|
|
kmip_debug(debug, "BIO_write failed");
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
}
|
|
*size += value_len;
|
|
|
|
if ((value_len % KMIP_TTLV_BLOCK_LENGTH) != 0) {
|
|
pad_len = KMIP_TTLV_BLOCK_LENGTH -
|
|
(value_len % KMIP_TTLV_BLOCK_LENGTH);
|
|
|
|
kmip_debug(debug, "pad_len: %lu", pad_len);
|
|
if (BIO_write(bio, padding, pad_len) != (int)pad_len) {
|
|
kmip_debug(debug, "BIO_write failed (padding)");
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
*size += pad_len;
|
|
}
|
|
|
|
kmip_debug(debug, "size: %lu", *size);
|
|
|
|
rc = 0;
|
|
out:
|
|
if (node->type == KMIP_TYPE_BIG_INTEGER)
|
|
free(value);
|
|
|
|
return rc;
|
|
}
|
|
|