Files
s390-tools/libkmipclient/utils.c
Ingo Franzki cc6f1a6d7b libkmipclient: Fix kmip_parse_decimal_uint() to reject negative numbers
Function strtoull() silently accepts a leading '-' and returns a large
positive number (two's complement wrap). Reject this by checking for
the minus character.

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

773 lines
16 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.
*/
#define _XOPEN_SOURCE
#define _DEFAULT_SOURCE
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "utils.h"
#include "names.h"
/**
* Print a debug message
*/
void kmip_print_debug(const char *func, const char *fmt, ...)
{
char tmp_fmt[200];
va_list ap;
if (snprintf(tmp_fmt, sizeof(tmp_fmt), "DBG: %s: %s", func, fmt) >
(int)sizeof(tmp_fmt))
return;
va_start(ap, fmt);
vwarnx(tmp_fmt, ap);
va_end(ap);
}
/**
* Parse a decimal string into a 64 bit signed value
*/
int kmip_parse_decimal_int(const char *str, int64_t *val)
{
long long v;
char *endptr;
if (str == NULL)
return -EINVAL;
errno = 0;
v = strtoll(str, &endptr, 10);
if ((errno == ERANGE && (v == LLONG_MAX || v == LLONG_MIN)) ||
(errno != 0 && v == 0))
return -EBADMSG;
if (endptr == str || *endptr != 0)
return -EBADMSG;
*val = v;
return 0;
}
/**
* Parse a decimal string into a 64 bit unsigned value
*/
int kmip_parse_decimal_uint(const char *str, uint64_t *val)
{
unsigned long long v;
char *endptr;
if (str == NULL)
return -EINVAL;
if (str[0] == '-')
return -EBADMSG;
errno = 0;
v = strtoull(str, &endptr, 10);
if ((errno == ERANGE && (v == 0 || v == ULLONG_MAX)) ||
(errno != 0 && v == 0))
return -EBADMSG;
if (endptr == str || *endptr != 0)
return -EBADMSG;
*val = v;
return 0;
}
/**
* Parse a hex string into a 64 bit signed value
*/
int kmip_parse_hex_int(const char *str, int64_t *val)
{
long long v;
char *endptr;
if (str == NULL)
return -EINVAL;
if (strncmp(str, "0x", 2) != 0)
return -EBADMSG;
errno = 0;
v = strtoll(str, &endptr, 16);
if ((errno == ERANGE && (v == LLONG_MAX || v == LLONG_MIN)) ||
(errno != 0 && v == 0))
return -EBADMSG;
if (endptr == str || *endptr != 0)
return -EBADMSG;
*val = v;
return 0;
}
/**
* Parse a hex string into a variable length signed big integer.
* On return, val and length is set. The buffer returned in val must be freed
* by the caller.
*/
int kmip_parse_hex(const char *str, bool has_prefix, unsigned char **val,
uint32_t *length)
{
unsigned char *buf;
BIGNUM *b = NULL;
int len;
if (str == NULL)
return -EINVAL;
if (has_prefix && strncmp(str, "0x", 2) != 0)
return -EBADMSG;
len = BN_hex2bn(&b, str + (has_prefix ? 2 : 0));
if (len <= 0)
return -EBADMSG;
if (len < (int)strlen(str) - (has_prefix ? 2 : 0))
return -EBADMSG;
len = len / 2 + (len % 2 > 0 ? 1 : 0);
buf = calloc(1, len);
if (buf == NULL) {
BN_free(b);
return -ENOMEM;
}
if (BN_bn2binpad(b, buf, len) != len) {
BN_free(b);
free(buf);
return -EIO;
}
*val = buf;
*length = len;
BN_free(b);
return 0;
}
/**
* Format a hex string from the byte array specified in val. The caller must
* free the returned str.
*/
int kmip_format_hex(const unsigned char *val, size_t length, bool prefix,
char **str)
{
size_t str_len, i;
char tmp[4];
char *ret;
if (length > (SIZE_MAX - ((prefix ? 2 : 0) + 1)) / 2)
return -EINVAL;
str_len = length * 2 + (prefix ? 2 : 0) + 1;
ret = calloc(1, str_len);
if (ret == NULL)
return -ENOMEM;
if (prefix)
strcat(ret, "0x");
for (i = 0; i < length; i++) {
sprintf(tmp, "%02x", val[i]);
strcat(ret, tmp);
}
*str = ret;
return 0;
}
/**
* Parse a hex string into a big number.
* On return, val and length is set. The buffer returned in val must be freed
* by the caller.
*/
int kmip_parse_bignum(const char *str, bool has_prefix, BIGNUM **bn)
{
unsigned char *buf;
uint32_t len;
int rc;
if (str == NULL)
return -EINVAL;
rc = kmip_parse_hex(str, has_prefix, &buf, &len);
if (rc != 0)
return rc;
rc = kmip_decode_bignum(buf, len, bn);
free(buf);
return rc;
}
/**
* Format a hex string from a big number. The caller must free the returned str.
*/
int kmip_format_bignum(const BIGNUM *bn, bool prefix, char **str)
{
unsigned char *buf;
uint32_t len, prev_len;
int rc;
len = kmip_encode_bignum_length(bn);
/* BIG INTEGERS must be a multiple of 8 bytes long */
if ((len % KMIP_BIG_INTEGER_BLOCK_LENGTH) != 0) {
prev_len = len;
len += KMIP_BIG_INTEGER_BLOCK_LENGTH -
(len % KMIP_BIG_INTEGER_BLOCK_LENGTH);
if (len < prev_len)
return -EOVERFLOW;
}
buf = malloc(len);
if (buf == NULL)
return -ENOMEM;
rc = kmip_encode_bignum(bn, buf, len);
if (rc != 0) {
free(buf);
return -EIO;
}
rc = kmip_format_hex(buf, len, prefix, str);
free(buf);
return rc;
}
/**
* Decode a binary big integer in two's complement form into an OpenSSL BIGNUM.
*/
int kmip_decode_bignum(const unsigned char *data, uint32_t length, BIGNUM **bn)
{
unsigned char *tmp = (unsigned char *)data;
int i, neg = 0, rc = 0;
if (data == NULL || bn == NULL)
return -EINVAL;
if (data[0] & 0x80) {
neg = 1;
tmp = calloc(1, length);
if (tmp == NULL)
return -ENOMEM;
for (i = 0; i < (int)length; i++)
tmp[i] = ~data[i];
for (i = length - 1; i >= 0; i--) {
tmp[i]++;
if (tmp[i] != 0x00)
break;
}
}
*bn = BN_bin2bn(tmp, length, NULL);
if (*bn == NULL) {
rc = -EIO;
goto out;
}
BN_set_negative(*bn, neg);
out:
if (neg)
free(tmp);
return rc;
}
/**
* Returns the length required by a binary big integer in two's complement form
*/
uint32_t kmip_encode_bignum_length(const BIGNUM *bn)
{
uint32_t length;
if (bn == NULL)
return 0;
length = BN_num_bytes(bn);
if (BN_is_negative(bn) && BN_is_bit_set(bn, (length * 8) - 1))
length += 1;
return length;
}
/**
* Encode an OpenSSL BIGNUM to a binary big integer in two's complement form,
* in the desired length.
*/
int kmip_encode_bignum(const BIGNUM *bn, unsigned char *data, uint32_t length)
{
int i;
if (bn == NULL || data == NULL)
return -EINVAL;
if (BN_bn2binpad(bn, data, length) != (int)length)
return -EIO;
if (BN_is_negative(bn)) {
for (i = 0; i < (int)length; i++)
data[i] = ~data[i];
for (i = length - 1; i >= 0; i--) {
data[i]++;
if (data[i] != 0x00)
break;
}
}
return 0;
}
/**
* Parse a timestamp in ISO8601 format and return it as time_t value
*/
int kmip_parse_timestamp(const char *str, int64_t *val)
{
struct tm tm = { 0 };
char *p;
int rc;
rc = kmip_parse_hex_int(str, val);
if (rc == 0)
return 0;
if (rc != -EBADMSG)
return rc;
p = strptime(str, KMIP_ISO8601_TIMESTAMP_TZ, &tm);
if (p == NULL)
p = strptime(str, KMIP_ISO8601_TIMESTAMP, &tm);
if (p == NULL || *p != 0)
return -EBADMSG;
/* Adjust according to the parsed time zone */
tm.tm_sec -= tm.tm_gmtoff;
tm.tm_gmtoff = 0;
tm.tm_isdst = 0;
*val = (time_t)timegm(&tm);
return 0;
}
/**
* Parses a mask specification of the specified tag and separator character
*/
int kmip_parse_mask(enum kmip_tag tag, const char *str, char separator,
int64_t *val)
{
const struct kmip_enum *info;
char *save_ptr, *s, *tok;
char delimiter[2];
uint32_t enum_val;
int rc = 0;
info = kmip_enum_info_by_tag(tag);
if (info == NULL)
return kmip_parse_hex_int(str, val);
*val = 0;
s = strdup(str);
if (s == NULL)
return -ENOMEM;
delimiter[0] = separator;
delimiter[1] = 0;
tok = strtok_r(s, delimiter, &save_ptr);
while (tok != NULL) {
rc = kmip_enum_value_by_name_or_hex(info, tok, &enum_val);
if (rc != 0)
break;
*val |= enum_val;
tok = strtok_r(NULL, delimiter, &save_ptr);
}
free(s);
return rc;
}
static int kmip_append_string(char **str, size_t *str_len, char separator,
const char *append)
{
size_t new_len;
char *tmp;
if (str == NULL || str_len == NULL)
return -EINVAL;
if (*str == NULL)
*str_len = 0;
new_len = *str_len;
if (*str == NULL)
new_len++;
else if (separator != 0)
new_len++;
if (append != NULL)
new_len += strlen(append);
if (new_len < *str_len)
return -EOVERFLOW;
tmp = realloc(*str, new_len);
if (tmp == NULL)
return -ENOMEM;
if (*str == NULL)
memset(tmp, 0, new_len);
else if (separator != 0)
strncat(tmp, &separator, 1);
if (append != NULL)
strcat(tmp, append);
*str = tmp;
*str_len = new_len;
return 0;
}
/**
* Format a mask specification of the specified tag and separator character
*/
int kmip_format_mask(enum kmip_tag tag, int32_t value, char separator,
char **str)
{
const struct kmip_enum *info;
char *s = NULL, *tmp;
size_t s_len = 0;
int rc = 0, i;
info = kmip_enum_info_by_tag(tag);
if (info == NULL || value == 0)
return kmip_format_hex((const unsigned char *)&value,
sizeof(value), true, str);
/* Process all known mask bits */
for (i = 0; value != 0 && info[i].name != NULL; i++) {
if (value & info[i].val) {
rc = kmip_append_string(&s, &s_len, separator,
info[i].name);
if (rc != 0)
goto out;
value &= ~info[i].val;
}
}
/* Any bits left in the value? */
if (value != 0) {
rc = kmip_format_hex((const unsigned char *)&value,
sizeof(value), true, &tmp);
if (rc != 0)
goto out;
rc = kmip_append_string(&s, &s_len, separator, tmp);
free(tmp);
if (rc != 0)
goto out;
}
*str = s;
out:
if (rc != 0)
free(s);
return rc;
}
void kmip_print_dump(const char *func, unsigned char *data, size_t size,
unsigned int indent)
{
char outstr[200], hexstr[4];
size_t i;
if (data == NULL)
return;
strcpy(outstr, "");
for (i = 0; i < size; i++) {
sprintf(hexstr, "%02x ", data[i]);
strcat(outstr, hexstr);
if (i % 16 == 15) {
kmip_print_debug(func, "%*s%s", indent, "",
outstr);
strcpy(outstr, "");
}
}
if (i % 16 != 0)
kmip_print_debug(func, "%*s%s", indent, "", outstr);
}
static void kmip_print_bignum(const char *func, const BIGNUM *bn,
unsigned int indent)
{
unsigned char *buf;
uint32_t len;
int rc;
if (bn == NULL)
return;
len = kmip_encode_bignum_length(bn);
buf = malloc(len);
if (buf == NULL)
return;
rc = kmip_encode_bignum(bn, buf, len);
if (rc != 0) {
free(buf);
return;
}
kmip_print_dump(func, buf, len, indent);
free(buf);
}
static void kmip_node_dump_int(struct kmip_node *node, unsigned int indent)
{
enum kmip_tag tag, v1_attr_tag = 0;
struct kmip_node *element;
char outstr[200] = { 0 };
struct tm *tm;
const char *s;
char *tmp;
time_t t;
int rc;
if (node == NULL)
return;
s = kmip_tag_name_by_tag(node->tag);
kmip_print_debug("kmip_node_dump", "%*sTag: %s (0x%x)", indent, "",
s ? s : "UNKNOWN", node->tag);
s = kmip_type_name_by_type(node->type);
kmip_print_debug("kmip_node_dump", "%*s Type: %s (0x%x)", indent, "",
s ? s : "UNKNOWN", node->type);
if (node->name != NULL)
kmip_print_debug("kmip_node_dump", "%*s Name: %s", indent,
"", node->name);
/*
* KMIP v1.x attribute values may be Enumerations or Integer Masks.
* To correctly print 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:
kmip_print_debug("kmip_node_dump", "%*s Elements (%u):",
indent, "",
kmip_node_get_structure_element_count(node));
element = node->structure_value;
while (element != NULL) {
kmip_node_dump_int(element, indent + 4);
element = element->next;
}
break;
case KMIP_TYPE_INTEGER:
if (kmip_is_tag_mask(tag)) {
rc = kmip_format_mask(tag, node->integer_value,
'|', &tmp);
if (rc == 0) {
kmip_print_debug("kmip_node_dump", "%*s "
"Value: %s (0x%x)",
indent, "", tmp,
node->integer_value);
free(tmp);
break;
}
}
kmip_print_debug("kmip_node_dump", "%*s Value: %d (0x%x)",
indent, "", node->integer_value,
node->integer_value);
break;
case KMIP_TYPE_LONG_INTEGER:
kmip_print_debug("kmip_node_dump", "%*s Value: %ld (0x%lx)",
indent, "", node->long_value,
node->long_value);
break;
case KMIP_TYPE_BIG_INTEGER:
kmip_print_debug("kmip_node_dump", "%*s Value: (%u bytes)",
indent, "", kmip_encode_bignum_length(
node->big_integer_value));
kmip_print_bignum("kmip_node_dump", node->big_integer_value,
indent + 4);
break;
case KMIP_TYPE_ENUMERATION:
s = kmip_enum_name_by_tag_value(tag, node->enumeration_value);
kmip_print_debug("kmip_node_dump", "%*s Value: %s (0x%x)",
indent, "", s ? s : "UNKNOWN",
node->enumeration_value);
break;
case KMIP_TYPE_BOOLEAN:
kmip_print_debug("kmip_node_dump", "%*s Value: %s", indent, "",
node->boolean_value ? "True" : "False");
break;
case KMIP_TYPE_TEXT_STRING:
kmip_print_debug("kmip_node_dump", "%*s Value: '%s' "
"(%u characters)", indent, "",
node->text_value, strlen(node->text_value));
break;
case KMIP_TYPE_BYTE_STRING:
kmip_print_debug("kmip_node_dump", "%*s Value: (%u bytes)",
indent, "", node->length);
kmip_print_dump("kmip_node_dump", node->bytes_value,
node->length, indent + 4);
break;
case KMIP_TYPE_DATE_TIME:
tm = gmtime((time_t *)&node->date_time_value);
if (tm != NULL)
strftime(outstr, sizeof(outstr),
KMIP_ISO8601_TIMESTAMP_UTC, tm);
else
strcpy(outstr, "INVALID");
kmip_print_debug("kmip_node_dump", "%*s Value: %s (0x%lx)",
indent, "", outstr, node->date_time_value);
break;
case KMIP_TYPE_INTERVAL:
kmip_print_debug("kmip_node_dump", "%*s Value: %d (0x%x)",
indent, "", node->interval_value,
node->interval_value);
break;
case KMIP_TYPE_DATE_TIME_EXTENDED:
t = (time_t)node->date_time_ext_value / 1000000;
tm = gmtime(&t);
if (tm != NULL)
strftime(outstr, sizeof(outstr),
KMIP_ISO8601_TIMESTAMP_UTC, tm);
else
strcpy(outstr, "INVALID");
kmip_print_debug("kmip_node_dump", "%*s Value: %s %lu (0x%lx)",
indent, "", outstr,
node->date_time_ext_value % 1000000,
node->date_time_ext_value);
break;
default:
break;
}
}
/**
* Dump a KMIP node
*
* @param node the node to free
* @param debug if false, the function is a no-op
*/
void kmip_node_dump(struct kmip_node *node, bool debug)
{
if (node == NULL || !debug)
return;
kmip_node_dump_int(node, 0);
}
/**
* Find a KMIP v1.x Attribute Name node in the elements of the specified parent
* node, and return the tag value of the attribute name.
*
* @param parent the parent node of the attribute name and value
*
* @returns the tag value of the attribute name, or 0 if not found, or unknown
* attribute name
*/
enum kmip_tag kmip_find_v1_attribute_name_tag(struct kmip_node *parent)
{
struct kmip_node *e;
if (parent == NULL)
return 0;
if (parent->tag != KMIP_TAG_ATTRIBUTE)
return 0;
if (parent->type != KMIP_TYPE_STRUCTURE)
return 0;
e = parent->structure_value;
while (e != NULL) {
/*
* A KMIP v2.x Vendor Attribute looks similar to a KMIP v1.x
* Attribute, but has a Vendor Identification node. If we find
* a Vendor Identification node, then it can't be a KMIP v1.x
* Attribute.
*/
if (e->tag == KMIP_TAG_VENDOR_IDENTIFICATION &&
e->type == KMIP_TYPE_TEXT_STRING)
return 0;
if (e->tag == KMIP_TAG_ATTRIBUTE_NAME &&
e->type == KMIP_TYPE_TEXT_STRING)
return kmip_attr_tag_by_v1_attr_name(e->text_value);
e = e->next;
}
return 0;
}
FILE *fopen_nofollow(const char *path, const char *mode)
{
int flags = O_NOFOLLOW;
int fd;
FILE *fp;
/* Determine flags based on mode */
if (mode[0] == 'r')
flags |= (mode[1] == '+') ? O_RDWR : O_RDONLY;
else if (mode[0] == 'w')
flags |= O_CREAT | O_TRUNC |
((mode[1] == '+') ? O_RDWR : O_WRONLY);
else if (mode[0] == 'a')
flags |= O_CREAT | O_APPEND |
((mode[1] == '+') ? O_RDWR : O_WRONLY);
else
return NULL;
fd = open(path, flags, 0600);
if (fd < 0)
return NULL;
fp = fdopen(fd, mode);
if (fp == NULL) {
close(fd);
return NULL;
}
return fp;
}