Files
s390-tools/libkmipclient/xml.c
Ingo Franzki 5fdeaab3d0 libkmipclient: Limit the nesting level of KMIP STRUCTURE recursion
KMIP STRUCTURE elements can be nested, which causes a recursion of
functions kmip_decode_ttlv(), kmip_decode_xml(), and kmip_decode_json().
A malformed KMIP response may thus cause stack exhaustion.

Limit the KMIP STRUCTURE nesting level to 32 levels. This is more than
enough for currently defined KMIP responses. The practically used
nesting level is 8 or 9, dependent on the type of KMIP response.

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>
2026-07-20 15:25:58 +02:00

499 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 "names.h"
#include "utils.h"
#define KMIP_XML_TTLV "TTLV"
#define KMIP_XML_TAG "tag"
#define KMIP_XML_NAME "name"
#define KMIP_XML_TYPE "type"
#define KMIP_XML_VALUE "value"
/**
* Decode a KMIP node from the data in the XML node using the XML encoding.
*
* @param xml the XML node to decode
* @param parent the parent node or NULL if no parent exists.
* @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_xml(const xmlNode *xml, struct kmip_node *parent,
struct kmip_node **node, size_t max_nesting_level,
bool debug)
{
char *tag_attr = NULL, *name_attr = NULL, *type_attr = NULL;
enum kmip_tag tag, v1_attr_tag = 0;
char *tag_name, *value_attr = NULL;
struct kmip_node *n = NULL, *e;
uint64_t uint64;
xmlNode *child;
int64_t int64;
int rc = 0, i;
if (max_nesting_level == 0)
return -E2BIG;
if (xml == NULL || node == NULL)
return -EINVAL;
if (xml->type != XML_ELEMENT_NODE) {
kmip_debug(debug, "Invalid XML node type: %d", xml->type);
return -EINVAL;
}
n = calloc(1, sizeof(struct kmip_node));
if (n == NULL) {
kmip_debug(debug, "calloc failed");
return -ENOMEM;
}
n->ref_count = 1;
if (strcmp((char *)xml->name, KMIP_XML_TTLV) == 0) {
tag_attr = (char *)xmlGetProp(xml, (xmlChar *)KMIP_XML_TAG);
if (tag_attr == NULL) {
kmip_debug(debug, "Missing '%s' attribute in XML node",
KMIP_XML_TAG);
rc = -EBADMSG;
goto out;
}
tag_name = tag_attr;
} else {
tag_name = (char *)xml->name;
}
n->tag = kmip_tag_by_name_or_hex(tag_name);
if (n->tag == 0) {
kmip_debug(debug, "Unknown 'tag' in XML object: '%s'",
tag_name);
rc = -EBADMSG;
goto out;
}
name_attr = (char *)xmlGetProp(xml, (xmlChar *)KMIP_XML_NAME);
if (name_attr != NULL)
n->name = strdup(name_attr);
type_attr = (char *)xmlGetProp(xml, (xmlChar *)KMIP_XML_TYPE);
if (type_attr == NULL) {
n->type = KMIP_TYPE_STRUCTURE;
} else {
n->type = kmip_type_by_name_or_hex(type_attr);
if (n->type == 0) {
kmip_debug(debug, "Unknown 'type' in XML object: '%s'",
type_attr);
rc = -EBADMSG;
goto out;
}
}
value_attr = (char *)xmlGetProp(xml, (xmlChar *)KMIP_XML_VALUE);
if (n->type != KMIP_TYPE_STRUCTURE && value_attr == NULL) {
kmip_debug(debug, "Missing '%s' attribute in XML node",
KMIP_XML_VALUE);
rc = -EBADMSG;
goto out;
}
/*
* KMIP v1.x attribute values may be Enumerations or Integer Masks.
* To correctly decode them, we need to know the tag. This is contained
* in a Attribute Name node, which is an element of our parent node.
*/
if (n->tag == KMIP_TAG_ATTRIBUTE_VALUE)
v1_attr_tag = kmip_find_v1_attribute_name_tag(parent);
tag = (v1_attr_tag != 0 ? v1_attr_tag : n->tag);
kmip_debug(debug, "tag: 0x%x type: 0x%x", n->tag, n->type);
switch (n->type) {
case KMIP_TYPE_STRUCTURE:
for (child = xml->children, i = 0; child != NULL;
child = child->next, i++) {
if (child->type != XML_ELEMENT_NODE)
continue;
rc = kmip_decode_xml(child, n, &e,
max_nesting_level - 1, debug);
if (rc != 0) {
kmip_debug(debug, "Failed to parse child "
"element %d", i);
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:
case KMIP_TYPE_LONG_INTEGER:
case KMIP_TYPE_DATE_TIME_EXTENDED:
if (n->type == KMIP_TYPE_INTEGER &&
kmip_is_tag_mask(tag)) {
rc = kmip_parse_mask(tag, value_attr, ' ', &int64);
if (rc != 0) {
kmip_debug(debug, "Failed to parse "
"mask string '%s'", value_attr);
goto out;
}
} else {
rc = kmip_parse_decimal_int(value_attr, &int64);
if (rc != 0) {
kmip_debug(debug, "Failed to parse "
"decimal string '%s'", value_attr);
goto out;
}
}
switch (n->type) {
case KMIP_TYPE_INTEGER:
n->integer_value = int64;
break;
case KMIP_TYPE_LONG_INTEGER:
n->long_value = int64;
break;
case KMIP_TYPE_DATE_TIME_EXTENDED:
n->date_time_ext_value = int64;
break;
default:
break;
}
break;
case KMIP_TYPE_INTERVAL:
rc = kmip_parse_decimal_uint(value_attr, &uint64);
if (rc != 0) {
kmip_debug(debug, "Failed to parse "
"decimal string '%s'", value_attr);
goto out;
}
n->interval_value = uint64;
break;
case KMIP_TYPE_BIG_INTEGER:
rc = kmip_parse_bignum(value_attr, false,
&n->big_integer_value);
if (rc != 0) {
kmip_debug(debug,
"Failed to parse bignum string '%s'",
value_attr);
goto out;
}
break;
case KMIP_TYPE_ENUMERATION:
rc = kmip_enum_value_by_tag_name_or_hex(tag, value_attr,
&n->enumeration_value);
if (rc != 0) {
kmip_debug(debug,
"Failed to parse enumeration '%s'",
value_attr);
goto out;
}
break;
case KMIP_TYPE_BOOLEAN:
n->boolean_value = (strcmp(value_attr, "true") == 0 ||
strcmp(value_attr, "1") == 0);
break;
case KMIP_TYPE_TEXT_STRING:
n->text_value = strdup(value_attr);
if (n->text_value == NULL) {
rc = -ENOMEM;
goto out;
}
n->length = strlen(n->text_value);
break;
case KMIP_TYPE_BYTE_STRING:
rc = kmip_parse_hex(value_attr, false, &n->bytes_value,
&n->length);
if (rc != 0) {
kmip_debug(debug,
"Failed to parse hex string '%s'",
value_attr);
goto out;
}
break;
case KMIP_TYPE_DATE_TIME:
rc = kmip_parse_timestamp(value_attr, &n->date_time_value);
if (rc != 0) {
kmip_debug(debug,
"Failed to parse time stamp '%s'",
value_attr);
goto out;
}
break;
default:
kmip_debug(debug, "unknown type: 0x%x", n->type);
rc = -EBADMSG;
goto out;
}
*node = n;
rc = 0;
out:
if (rc != 0 && n != NULL)
kmip_node_free(n);
if (tag_attr != NULL)
xmlFree(tag_attr);
if (name_attr != NULL)
xmlFree(name_attr);
if (type_attr != NULL)
xmlFree(type_attr);
if (value_attr != NULL)
xmlFree(value_attr);
return rc;
}
/**
* Encode a KMIP node into an XML node using the XML encoding.
*
* @param node the node to encode
* @param xml On return: the XML node
* @param debug if true, debug messages are printed
*
* @returns 0 in case of success, or a negative errno value
*/
int kmip_encode_xml(const struct kmip_node *node, xmlNode **xml, bool debug)
{
enum kmip_tag tag, v1_attr_tag = 0;
xmlNode *ret_xml = NULL, *elem_xml;
struct kmip_node *element;
const char *tag_name;
char tmp_str[50];
const char *str;
struct tm *tm;
xmlAttr *attr;
char *tmp;
int rc;
if (node == NULL || xml == NULL)
return -EINVAL;
kmip_debug(debug, "tag: 0x%x type: 0x%x", node->tag, node->type);
tag_name = kmip_tag_name_by_tag(node->tag);
if (tag_name != NULL)
ret_xml = xmlNewNode(NULL, (xmlChar *)tag_name);
else
ret_xml = xmlNewNode(NULL, (xmlChar *)KMIP_XML_TTLV);
if (ret_xml == NULL) {
kmip_debug(debug, "Failed to allocate a XML node");
return -ENOMEM;
}
if (tag_name == NULL) {
sprintf(tmp_str, "0x%06x", node->tag);
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_TAG,
(xmlChar *)tmp_str);
if (attr == NULL) {
kmip_debug(debug,
"Failed to add '%s' attribute to XML node",
KMIP_XML_TAG);
rc = -ENOMEM;
goto out;
}
if (node->name != NULL) {
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_NAME,
(xmlChar *)node->name);
if (attr == NULL) {
kmip_debug(debug, "Failed to add '%s' "
"attribute to XML node",
KMIP_XML_NAME);
rc = -ENOMEM;
goto out;
}
}
}
if (node->type != KMIP_TYPE_STRUCTURE) {
str = kmip_type_name_by_type(node->type);
if (str == NULL) {
kmip_debug(debug, "unknown type 0x%x", node->type);
rc = -EINVAL;
goto out;
}
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_TYPE,
(xmlChar *)str);
if (attr == NULL) {
kmip_debug(debug,
"Failed to add '%s' attribute to XML node",
KMIP_XML_TYPE);
rc = -ENOMEM;
goto out;
}
}
/*
* KMIP v1.x attribute values may be Enumerations or Integer Masks.
* To correctly encode them, we need to know the tag. This is contained
* in a Attribute Name node, which is an element of our parent node.
*/
if (node->tag == KMIP_TAG_ATTRIBUTE_VALUE)
v1_attr_tag = kmip_find_v1_attribute_name_tag(node->parent);
tag = (v1_attr_tag != 0 ? v1_attr_tag : node->tag);
switch (node->type) {
case KMIP_TYPE_STRUCTURE:
element = node->structure_value;
while (element != NULL) {
rc = kmip_encode_xml(element, &elem_xml, debug);
if (rc != 0) {
kmip_debug(debug, "kmip_encode_xml failed");
goto out;
}
if (xmlAddChild(ret_xml, elem_xml) == NULL) {
kmip_debug(debug, "xmlAddChild failed");
rc = -EIO;
goto out;
}
element = element->next;
}
attr = NULL;
break;
case KMIP_TYPE_INTEGER:
if (kmip_is_tag_mask(tag) && node->integer_value != 0) {
rc = kmip_format_mask(tag, node->integer_value,
' ', &tmp);
if (rc != 0) {
kmip_debug(debug, "kmip_format_mask failed");
goto out;
}
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp);
free(tmp);
} else {
sprintf(tmp_str, "%d", node->integer_value);
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp_str);
}
break;
case KMIP_TYPE_INTERVAL:
sprintf(tmp_str, "%u", node->interval_value);
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp_str);
break;
case KMIP_TYPE_LONG_INTEGER:
sprintf(tmp_str, "%ld", node->long_value);
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp_str);
break;
case KMIP_TYPE_BIG_INTEGER:
rc = kmip_format_bignum(node->big_integer_value, false, &tmp);
if (rc != 0) {
kmip_debug(debug, "kmip_format_bignum failed");
goto out;
}
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp);
free(tmp);
break;
case KMIP_TYPE_ENUMERATION:
str = kmip_enum_name_by_tag_value(tag, node->enumeration_value);
if (str != NULL) {
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)str);
} else {
sprintf(tmp_str, "0x%08x", node->enumeration_value);
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp_str);
}
break;
case KMIP_TYPE_BOOLEAN:
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)(node->boolean_value ?
"true" : "false"));
break;
case KMIP_TYPE_TEXT_STRING:
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)node->text_value);
break;
case KMIP_TYPE_BYTE_STRING:
rc = kmip_format_hex(node->bytes_value, node->length,
false, &tmp);
if (rc != 0) {
kmip_debug(debug, "kmip_format_hex_long failed");
goto out;
}
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp);
free(tmp);
break;
case KMIP_TYPE_DATE_TIME:
tm = gmtime((time_t *)&node->date_time_value);
strftime(tmp_str, sizeof(tmp_str), KMIP_ISO8601_TIMESTAMP_UTC,
tm);
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp_str);
break;
case KMIP_TYPE_DATE_TIME_EXTENDED:
sprintf(tmp_str, "%ld", node->date_time_ext_value);
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
(xmlChar *)tmp_str);
break;
default:
kmip_debug(debug, "unknown type: 0x%x", node->type);
rc = -EINVAL;
goto out;
}
if (attr == NULL && node->type != KMIP_TYPE_STRUCTURE) {
kmip_debug(debug, "Failed to add '%s' "
"attribute to XML node",
KMIP_XML_VALUE);
rc = -ENOMEM;
goto out;
}
rc = 0;
*xml = ret_xml;
out:
if (rc != 0)
xmlFreeNode(ret_xml);
return rc;
}