/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ #include "array.h" #include #include #include #include #include #include #include #include #include #include #include #define POINTER_CHECK(pointer) \ do { \ if ((pointer) == NULL) \ return PLDM_ERROR_INVALID_DATA; \ } while (0) #define ATTR_TYPE_EXPECT(type, expected) \ do { \ if ((type) != (expected) && (type) != ((expected) | 0x80)) \ return PLDM_ERROR_INVALID_DATA; \ } while (0) #define BUFFER_SIZE_EXPECT(current_size, expected_size) \ do { \ if ((current_size) < (expected_size)) \ return PLDM_ERROR_INVALID_LENGTH; \ } while (0) #define MEMBER_SIZE(type, member) sizeof(((struct type *)0)->member) static void set_errmsg(const char **errmsg, const char *msg) { if (errmsg != NULL) { *errmsg = msg; } } static int get_bios_string_handle(uint16_t *val) { static uint16_t handle = 0; assert(handle != UINT16_MAX); if (handle == UINT16_MAX) { return PLDM_ERROR_INVALID_DATA; } *val = handle++; return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE size_t pldm_bios_table_string_entry_encode_length(uint16_t string_length) { return sizeof(struct pldm_bios_string_table_entry) - MEMBER_SIZE(pldm_bios_string_table_entry, name) + string_length; } LIBPLDM_ABI_STABLE int pldm_bios_table_string_entry_encode(void *entry, size_t entry_length, const char *str, uint16_t str_length) { if (str_length == 0) { return PLDM_ERROR_INVALID_DATA; } POINTER_CHECK(entry); POINTER_CHECK(str); size_t length = pldm_bios_table_string_entry_encode_length(str_length); BUFFER_SIZE_EXPECT(entry_length, length); if (entry_length - (sizeof(struct pldm_bios_string_table_entry) - MEMBER_SIZE(pldm_bios_string_table_entry, name)) < str_length) { return PLDM_ERROR_INVALID_LENGTH; } struct pldm_bios_string_table_entry *string_entry = entry; uint16_t handle; int rc = get_bios_string_handle(&handle); if (rc != PLDM_SUCCESS) { return rc; } string_entry->string_handle = htole16(handle); string_entry->string_length = htole16(str_length); memcpy(string_entry->name, str, str_length); return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_string_entry_decode_handle( const struct pldm_bios_string_table_entry *entry) { return le16toh(entry->string_handle); } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_string_entry_decode_string_length( const struct pldm_bios_string_table_entry *entry) { return le16toh(entry->string_length); } LIBPLDM_ABI_STABLE int pldm_bios_table_string_entry_decode_string( const struct pldm_bios_string_table_entry *entry, char *buffer, size_t size) { POINTER_CHECK(entry); POINTER_CHECK(buffer); if (size == 0) { return PLDM_ERROR_INVALID_LENGTH; } size_t length = pldm_bios_table_string_entry_decode_string_length(entry); length = length < (size - 1) ? length : (size - 1); memcpy(buffer, entry->name, length); buffer[length] = 0; return PLDM_SUCCESS; } static ssize_t string_table_entry_length(const void *table_entry) { const struct pldm_bios_string_table_entry *entry = table_entry; size_t len = sizeof(*entry) - sizeof(entry->name) + pldm_bios_table_string_entry_decode_string_length(entry); if (len > SSIZE_MAX) { return -1; } return (ssize_t)len; } static int get_bios_attr_handle(uint16_t *val) { static uint16_t handle = 0; assert(handle != UINT16_MAX); if (handle == UINT16_MAX) { return PLDM_ERROR_INVALID_DATA; } *val = handle++; return PLDM_SUCCESS; } static int attr_table_entry_encode_header(void *entry, size_t length, uint8_t attr_type, uint16_t string_handle) { struct pldm_bios_attr_table_entry *attr_entry = entry; assert(sizeof(*attr_entry) <= length); if (sizeof(*attr_entry) > length) { return PLDM_ERROR_INVALID_LENGTH; } uint16_t handle; int rc = get_bios_attr_handle(&handle); if (rc != PLDM_SUCCESS) { return rc; } attr_entry->attr_handle = htole16(handle); attr_entry->attr_type = attr_type; attr_entry->string_handle = htole16(string_handle); return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_attr_entry_decode_attribute_handle( const struct pldm_bios_attr_table_entry *entry) { return le16toh(entry->attr_handle); } LIBPLDM_ABI_STABLE uint8_t pldm_bios_table_attr_entry_decode_attribute_type( const struct pldm_bios_attr_table_entry *entry) { return entry->attr_type; } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_attr_entry_decode_string_handle( const struct pldm_bios_attr_table_entry *entry) { return le16toh(entry->string_handle); } LIBPLDM_ABI_STABLE size_t pldm_bios_table_attr_entry_enum_encode_length(uint8_t pv_num, uint8_t def_num) { return sizeof(struct pldm_bios_attr_table_entry) - MEMBER_SIZE(pldm_bios_attr_table_entry, metadata) + sizeof(pv_num) + pv_num * sizeof(uint16_t) + sizeof(def_num) + def_num; } LIBPLDM_ABI_DEPRECATED_UNSAFE int pldm_bios_table_attr_entry_enum_encode( void *entry, size_t entry_length, const struct pldm_bios_table_attr_entry_enum_info *info) { POINTER_CHECK(entry); POINTER_CHECK(info); size_t length = pldm_bios_table_attr_entry_enum_encode_length( info->pv_num, info->def_num); BUFFER_SIZE_EXPECT(entry_length, length); uint8_t attr_type = info->read_only ? PLDM_BIOS_ENUMERATION_READ_ONLY : PLDM_BIOS_ENUMERATION; int rc = attr_table_entry_encode_header(entry, entry_length, attr_type, info->name_handle); if (rc != PLDM_SUCCESS) { return rc; } struct pldm_bios_attr_table_entry *attr_entry = entry; attr_entry->metadata[0] = info->pv_num; uint16_t *pv_hdls = (uint16_t *)(attr_entry->metadata + 1 /* sizeof(pv num) */); size_t i; for (i = 0; i < info->pv_num; i++) { pv_hdls[i] = htole16(info->pv_handle[i]); } attr_entry->metadata[1 + info->pv_num * sizeof(uint16_t)] = info->def_num; memcpy(attr_entry->metadata + 1 /* sizeof(pv num) */ + info->pv_num * sizeof(uint16_t) + 1 /* sizeof(def num)*/, info->def_index, info->def_num); return PLDM_SUCCESS; } #define ATTR_TYPE_EXPECT(type, expected) \ do { \ if ((type) != (expected) && (type) != ((expected) | 0x80)) \ return PLDM_ERROR_INVALID_DATA; \ } while (0) LIBPLDM_ABI_STABLE int pldm_bios_table_attr_entry_enum_decode_pv_num( const struct pldm_bios_attr_table_entry *entry, uint8_t *pv_num) { POINTER_CHECK(entry); POINTER_CHECK(pv_num); ATTR_TYPE_EXPECT(entry->attr_type, PLDM_BIOS_ENUMERATION); *pv_num = entry->metadata[0]; return PLDM_SUCCESS; } LIBPLDM_ABI_DEPRECATED_UNSAFE int pldm_bios_table_attr_entry_enum_decode_def_num( const struct pldm_bios_attr_table_entry *entry, uint8_t *def_num) { uint8_t pv_num; POINTER_CHECK(entry); POINTER_CHECK(def_num); ATTR_TYPE_EXPECT(entry->attr_type, PLDM_BIOS_ENUMERATION); pv_num = entry->metadata[0]; *def_num = entry->metadata[sizeof(uint8_t) + sizeof(uint16_t) * pv_num]; return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_entry_enum_decode_pv_hdls( const struct pldm_bios_attr_table_entry *entry, uint16_t *pv_hdls, uint8_t pv_num) { POINTER_CHECK(entry); POINTER_CHECK(pv_hdls); ATTR_TYPE_EXPECT(entry->attr_type, PLDM_BIOS_ENUMERATION); uint8_t num = entry->metadata[0]; num = num < pv_num ? num : pv_num; size_t i; for (i = 0; i < num; i++) { uint16_t *hdl = (uint16_t *)(entry->metadata + sizeof(uint8_t) + i * sizeof(uint16_t)); pv_hdls[i] = le16toh(*hdl); } return PLDM_SUCCESS; } LIBPLDM_ABI_DEPRECATED_UNSAFE uint8_t pldm_bios_table_attr_entry_enum_decode_def_indices( const struct pldm_bios_attr_table_entry *entry, uint8_t *def_indices, uint8_t def_num) { uint8_t num = 0; /* TODO: Fix the API to propagate errors */ pldm_bios_table_attr_entry_enum_decode_def_num(entry, &num); num = num < def_num ? num : def_num; uint8_t pv_num = entry->metadata[0]; const uint8_t *p = entry->metadata + sizeof(uint8_t) /* number of possible values*/ + pv_num * sizeof(uint16_t) /* possible values */ + sizeof(uint8_t); /* number of default values */ memcpy(def_indices, p, num); return num; } /** @brief Get length of an enum attribute entry */ static ssize_t attr_table_entry_length_enum(const void *arg) { const struct pldm_bios_attr_table_entry *entry = arg; uint8_t pv_num = entry->metadata[0]; uint8_t def_num = 0; /* TODO: Fix the API to propagate errors */ pldm_bios_table_attr_entry_enum_decode_def_num(entry, &def_num); size_t len = pldm_bios_table_attr_entry_enum_encode_length(pv_num, def_num); if (len > SSIZE_MAX) { return -1; } return (ssize_t)len; } struct attr_table_string_entry_fields { uint8_t string_type; uint16_t min_length; uint16_t max_length; uint16_t def_length; uint8_t def_string[1]; } __attribute__((packed)); LIBPLDM_ABI_STABLE size_t pldm_bios_table_attr_entry_string_encode_length(uint16_t def_str_len) { return sizeof(struct pldm_bios_attr_table_entry) - MEMBER_SIZE(pldm_bios_attr_table_entry, metadata) + sizeof(struct attr_table_string_entry_fields) - MEMBER_SIZE(attr_table_string_entry_fields, def_string) + def_str_len; } #define PLDM_STRING_TYPE_MAX 5 #define PLDM_STRING_TYPE_VENDOR 0xff LIBPLDM_ABI_STABLE int pldm_bios_table_attr_entry_string_info_check( const struct pldm_bios_table_attr_entry_string_info *info, const char **errmsg) { if (info->min_length > info->max_length) { set_errmsg(errmsg, "MinimumStingLength should not be greater " "than MaximumStringLength"); return PLDM_ERROR_INVALID_DATA; } if (info->min_length == info->max_length && info->def_length != info->min_length) { set_errmsg(errmsg, "Wrong DefaultStringLength"); return PLDM_ERROR_INVALID_DATA; } if (info->def_length > info->max_length || info->def_length < info->min_length) { set_errmsg(errmsg, "Wrong DefaultStringLength"); return PLDM_ERROR_INVALID_DATA; } if (info->string_type > PLDM_STRING_TYPE_MAX && info->string_type != PLDM_STRING_TYPE_VENDOR) { set_errmsg(errmsg, "Wrong StringType"); return PLDM_ERROR_INVALID_DATA; } if (info->def_string && info->def_length != strlen(info->def_string)) { set_errmsg(errmsg, "Length of DefaultString should be equal to " "DefaultStringLength"); return PLDM_ERROR_INVALID_DATA; } return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_entry_string_encode( void *entry, size_t entry_length, const struct pldm_bios_table_attr_entry_string_info *info) { POINTER_CHECK(entry); POINTER_CHECK(info); size_t length = pldm_bios_table_attr_entry_string_encode_length( info->def_length); BUFFER_SIZE_EXPECT(entry_length, length); if (pldm_bios_table_attr_entry_string_info_check(info, NULL) != PLDM_SUCCESS) { return PLDM_ERROR_INVALID_DATA; } uint8_t attr_type = info->read_only ? PLDM_BIOS_STRING_READ_ONLY : PLDM_BIOS_STRING; int rc = attr_table_entry_encode_header(entry, entry_length, attr_type, info->name_handle); if (rc != PLDM_SUCCESS) { return rc; } struct pldm_bios_attr_table_entry *attr_entry = entry; struct attr_table_string_entry_fields *attr_fields = (struct attr_table_string_entry_fields *)attr_entry->metadata; attr_fields->string_type = info->string_type; attr_fields->min_length = htole16(info->min_length); attr_fields->max_length = htole16(info->max_length); attr_fields->def_length = htole16(info->def_length); if (info->def_length != 0 && info->def_string != NULL) { memcpy(attr_fields->def_string, info->def_string, info->def_length); } return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_entry_string_decode_def_string_length( const struct pldm_bios_attr_table_entry *entry, uint16_t *def_string_length) { POINTER_CHECK(entry); POINTER_CHECK(def_string_length); ATTR_TYPE_EXPECT(entry->attr_type, PLDM_BIOS_STRING); struct attr_table_string_entry_fields *fields = (struct attr_table_string_entry_fields *)entry->metadata; *def_string_length = le16toh(fields->def_length); return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE uint8_t pldm_bios_table_attr_entry_string_decode_string_type( const struct pldm_bios_attr_table_entry *entry) { struct attr_table_string_entry_fields *fields = (struct attr_table_string_entry_fields *)entry->metadata; return fields->string_type; } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_attr_entry_string_decode_max_length( const struct pldm_bios_attr_table_entry *entry) { struct attr_table_string_entry_fields *fields = (struct attr_table_string_entry_fields *)entry->metadata; return le16toh(fields->max_length); } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_attr_entry_string_decode_min_length( const struct pldm_bios_attr_table_entry *entry) { struct attr_table_string_entry_fields *fields = (struct attr_table_string_entry_fields *)entry->metadata; return le16toh(fields->min_length); } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_attr_entry_string_decode_def_string( const struct pldm_bios_attr_table_entry *entry, char *buffer, size_t size) { uint16_t length = 0; int rc; if (!entry || !buffer || size == 0) { return 0; } /* TODO: Rework the API to propagate the error */ rc = pldm_bios_table_attr_entry_string_decode_def_string_length( entry, &length); if (rc != PLDM_SUCCESS) { return 0; } length = length < (size - 1) ? length : (size - 1); struct attr_table_string_entry_fields *fields = (struct attr_table_string_entry_fields *)entry->metadata; memcpy(buffer, fields->def_string, length); buffer[length] = 0; return length; } /** @brief Get length of a string attribute entry */ static ssize_t attr_table_entry_length_string(const void *entry) { uint16_t def_str_len = 0; /* TODO: Rework the API to propagate the error */ pldm_bios_table_attr_entry_string_decode_def_string_length( entry, &def_str_len); size_t len = pldm_bios_table_attr_entry_string_encode_length(def_str_len); if (len > SSIZE_MAX) { return -1; } return (ssize_t)len; } struct attr_table_integer_entry_fields { uint64_t lower_bound; uint64_t upper_bound; uint32_t scalar_increment; uint64_t default_value; } __attribute__((packed)); LIBPLDM_ABI_STABLE size_t pldm_bios_table_attr_entry_integer_encode_length(void) { return sizeof(struct pldm_bios_attr_table_entry) - 1 + sizeof(struct attr_table_integer_entry_fields); } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_entry_integer_info_check( const struct pldm_bios_table_attr_entry_integer_info *info, const char **errmsg) { if (info->lower_bound == info->upper_bound) { if (info->default_value != info->lower_bound) { set_errmsg(errmsg, "Wrong DefaultValue"); return PLDM_ERROR_INVALID_DATA; } if (info->scalar_increment != 0) { set_errmsg(errmsg, "Wrong ScalarIncrement"); return PLDM_ERROR_INVALID_DATA; } return PLDM_SUCCESS; } if (info->lower_bound > info->upper_bound) { set_errmsg(errmsg, "LowerBound should not be greater than UpperBound"); return PLDM_ERROR_INVALID_DATA; } if (info->default_value > info->upper_bound || info->default_value < info->lower_bound) { set_errmsg(errmsg, "Wrong DefaultValue"); return PLDM_ERROR_INVALID_DATA; } if (info->scalar_increment == 0) { set_errmsg(errmsg, "ScalarIncrement should not be zero when " "lower_bound != upper_bound"); return PLDM_ERROR_INVALID_DATA; } if ((info->default_value - info->lower_bound) % info->scalar_increment != 0) { set_errmsg(errmsg, "Wrong DefaultValue or ScalarIncrement"); return PLDM_ERROR_INVALID_DATA; } return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_entry_integer_encode( void *entry, size_t entry_length, const struct pldm_bios_table_attr_entry_integer_info *info) { POINTER_CHECK(entry); POINTER_CHECK(info); size_t length = pldm_bios_table_attr_entry_integer_encode_length(); BUFFER_SIZE_EXPECT(entry_length, length); if (pldm_bios_table_attr_entry_integer_info_check(info, NULL) != PLDM_SUCCESS) { return PLDM_ERROR_INVALID_DATA; } uint8_t attr_type = info->read_only ? PLDM_BIOS_INTEGER_READ_ONLY : PLDM_BIOS_INTEGER; int rc = attr_table_entry_encode_header(entry, entry_length, attr_type, info->name_handle); if (rc != PLDM_SUCCESS) { return rc; } struct pldm_bios_attr_table_entry *attr_entry = entry; struct attr_table_integer_entry_fields *attr_fields = (struct attr_table_integer_entry_fields *)attr_entry->metadata; attr_fields->lower_bound = htole64(info->lower_bound); attr_fields->upper_bound = htole64(info->upper_bound); attr_fields->scalar_increment = htole32(info->scalar_increment); attr_fields->default_value = htole64(info->default_value); return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE void pldm_bios_table_attr_entry_integer_decode( const struct pldm_bios_attr_table_entry *entry, uint64_t *lower, uint64_t *upper, uint32_t *scalar, uint64_t *def) { struct attr_table_integer_entry_fields *fields = (struct attr_table_integer_entry_fields *)entry->metadata; *lower = le64toh(fields->lower_bound); *upper = le64toh(fields->upper_bound); *scalar = le32toh(fields->scalar_increment); *def = le64toh(fields->default_value); } static ssize_t attr_table_entry_length_integer(const void *entry) { (void)entry; size_t len = pldm_bios_table_attr_entry_integer_encode_length(); if (len > SSIZE_MAX) { return -1; } return (ssize_t)len; } struct table_entry_length { uint8_t attr_type; ssize_t (*entry_length_handler)(const void *); }; static const struct table_entry_length * find_table_entry_length_by_type(uint8_t attr_type, const struct table_entry_length *handlers, size_t count) { size_t i; for (i = 0; i < count; i++) { if (attr_type == handlers[i].attr_type) { return &handlers[i]; } } return NULL; } static const struct table_entry_length attr_table_entries[] = { { .attr_type = PLDM_BIOS_ENUMERATION, .entry_length_handler = attr_table_entry_length_enum }, { .attr_type = PLDM_BIOS_ENUMERATION_READ_ONLY, .entry_length_handler = attr_table_entry_length_enum }, { .attr_type = PLDM_BIOS_STRING, .entry_length_handler = attr_table_entry_length_string }, { .attr_type = PLDM_BIOS_STRING_READ_ONLY, .entry_length_handler = attr_table_entry_length_string }, { .attr_type = PLDM_BIOS_INTEGER, .entry_length_handler = attr_table_entry_length_integer }, { .attr_type = PLDM_BIOS_INTEGER_READ_ONLY, .entry_length_handler = attr_table_entry_length_integer }, }; static ssize_t attr_table_entry_length(const void *table_entry) { const struct pldm_bios_attr_table_entry *entry = table_entry; const struct table_entry_length *attr_table_entry = find_table_entry_length_by_type(entry->attr_type, attr_table_entries, ARRAY_SIZE(attr_table_entries)); assert(attr_table_entry != NULL); if (!attr_table_entry) { return -1; } assert(attr_table_entry->entry_length_handler != NULL); if (!attr_table_entry->entry_length_handler) { return -1; } return attr_table_entry->entry_length_handler(entry); } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_attr_value_entry_decode_attribute_handle( const struct pldm_bios_attr_val_table_entry *entry) { return le16toh(entry->attr_handle); } LIBPLDM_ABI_STABLE uint8_t pldm_bios_table_attr_value_entry_decode_attribute_type( const struct pldm_bios_attr_val_table_entry *entry) { return entry->attr_type; } LIBPLDM_ABI_STABLE size_t pldm_bios_table_attr_value_entry_encode_enum_length(uint8_t count) { return sizeof(struct pldm_bios_attr_val_table_entry) - 1 + sizeof(count) + count; } LIBPLDM_ABI_STABLE uint8_t pldm_bios_table_attr_value_entry_enum_decode_number( const struct pldm_bios_attr_val_table_entry *entry) { return entry->value[0]; } LIBPLDM_ABI_STABLE uint8_t pldm_bios_table_attr_value_entry_enum_decode_handles( const struct pldm_bios_attr_val_table_entry *entry, uint8_t *handles, uint8_t number) { uint8_t curr_num = pldm_bios_table_attr_value_entry_enum_decode_number(entry); curr_num = number < curr_num ? number : curr_num; memcpy(handles, &entry->value[1], curr_num); return curr_num; } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_value_entry_encode_enum( void *entry, size_t entry_length, uint16_t attr_handle, uint8_t attr_type, uint8_t count, const uint8_t *handles) { struct pldm_bios_attr_val_table_entry *table_entry; POINTER_CHECK(entry); POINTER_CHECK(handles); if (count != 0 && handles == NULL) { return PLDM_ERROR_INVALID_DATA; } ATTR_TYPE_EXPECT(attr_type, PLDM_BIOS_ENUMERATION); BUFFER_SIZE_EXPECT(entry_length, sizeof(*table_entry)); table_entry = entry; table_entry->attr_handle = htole16(attr_handle); table_entry->attr_type = attr_type; table_entry->value[0] = count; if (entry_length - sizeof(*table_entry) < count) { return PLDM_ERROR_INVALID_LENGTH; } memcpy(&table_entry->value[1], handles, count); return PLDM_SUCCESS; } static ssize_t attr_value_table_entry_length_enum(const void *entry) { uint8_t number = pldm_bios_table_attr_value_entry_enum_decode_number(entry); size_t len = pldm_bios_table_attr_value_entry_encode_enum_length(number); if (len > SSIZE_MAX) { return -1; } return (ssize_t)len; } LIBPLDM_ABI_STABLE size_t pldm_bios_table_attr_value_entry_encode_string_length(uint16_t string_length) { return sizeof(struct pldm_bios_attr_val_table_entry) - 1 + sizeof(string_length) + string_length; } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_attr_value_entry_string_decode_length( const struct pldm_bios_attr_val_table_entry *entry) { uint16_t str_length = 0; memcpy(&str_length, entry->value, sizeof(str_length)); return le16toh(str_length); } LIBPLDM_ABI_STABLE void pldm_bios_table_attr_value_entry_string_decode_string( const struct pldm_bios_attr_val_table_entry *entry, struct variable_field *current_string) { current_string->length = pldm_bios_table_attr_value_entry_string_decode_length(entry); current_string->ptr = entry->value + sizeof(uint16_t); // sizeof(CurrentStringLength) } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_value_entry_encode_string( void *entry, size_t entry_length, uint16_t attr_handle, uint8_t attr_type, uint16_t str_length, const char *str) { struct pldm_bios_attr_val_table_entry *table_entry; POINTER_CHECK(entry); if (str_length != 0 && str == NULL) { return PLDM_ERROR_INVALID_DATA; } ATTR_TYPE_EXPECT(attr_type, PLDM_BIOS_STRING); BUFFER_SIZE_EXPECT(entry_length, (sizeof(*table_entry) - 1 + sizeof(str_length))); table_entry = entry; table_entry->attr_handle = htole16(attr_handle); table_entry->attr_type = attr_type; if (entry_length - (sizeof(*table_entry) - 1 + sizeof(str_length)) < str_length) { return PLDM_ERROR_INVALID_LENGTH; } memcpy(table_entry->value + sizeof(str_length), str, str_length); str_length = htole16(str_length); memcpy(table_entry->value, &str_length, sizeof(str_length)); return PLDM_SUCCESS; } static ssize_t attr_value_table_entry_length_string(const void *entry) { uint16_t str_length = pldm_bios_table_attr_value_entry_string_decode_length(entry); size_t len = pldm_bios_table_attr_value_entry_encode_string_length( str_length); if (len > SSIZE_MAX) { return -1; } return (ssize_t)len; } LIBPLDM_ABI_STABLE size_t pldm_bios_table_attr_value_entry_encode_integer_length(void) { return sizeof(struct pldm_bios_attr_val_table_entry) - 1 + sizeof(uint64_t); } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_value_entry_encode_integer(void *entry, size_t entry_length, uint16_t attr_handle, uint8_t attr_type, uint64_t cv) { POINTER_CHECK(entry); size_t length = pldm_bios_table_attr_value_entry_encode_integer_length(); ATTR_TYPE_EXPECT(attr_type, PLDM_BIOS_INTEGER); BUFFER_SIZE_EXPECT(entry_length, length); struct pldm_bios_attr_val_table_entry *table_entry = entry; table_entry->attr_handle = htole16(attr_handle); table_entry->attr_type = attr_type; cv = htole64(cv); memcpy(table_entry->value, &cv, sizeof(uint64_t)); return PLDM_SUCCESS; } LIBPLDM_ABI_STABLE uint64_t pldm_bios_table_attr_value_entry_integer_decode_cv( const struct pldm_bios_attr_val_table_entry *entry) { uint64_t cv = 0; memcpy(&cv, entry->value, sizeof(cv)); cv = le64toh(cv); return cv; } static ssize_t attr_value_table_entry_length_integer(const void *entry) { (void)entry; size_t len = pldm_bios_table_attr_value_entry_encode_integer_length(); if (len > SSIZE_MAX) { return -1; } return (ssize_t)len; } static const struct table_entry_length attr_value_table_entries[] = { { .attr_type = PLDM_BIOS_ENUMERATION, .entry_length_handler = attr_value_table_entry_length_enum }, { .attr_type = PLDM_BIOS_ENUMERATION_READ_ONLY, .entry_length_handler = attr_value_table_entry_length_enum }, { .attr_type = PLDM_BIOS_STRING, .entry_length_handler = attr_value_table_entry_length_string }, { .attr_type = PLDM_BIOS_STRING_READ_ONLY, .entry_length_handler = attr_value_table_entry_length_string }, { .attr_type = PLDM_BIOS_INTEGER, .entry_length_handler = attr_value_table_entry_length_integer }, { .attr_type = PLDM_BIOS_INTEGER_READ_ONLY, .entry_length_handler = attr_value_table_entry_length_integer }, }; static ssize_t attr_value_table_entry_length(const void *table_entry) { const struct pldm_bios_attr_val_table_entry *entry = table_entry; const struct table_entry_length *entry_length = find_table_entry_length_by_type( entry->attr_type, attr_value_table_entries, ARRAY_SIZE(attr_value_table_entries)); assert(entry_length != NULL); if (!entry_length) { return -1; } assert(entry_length->entry_length_handler != NULL); if (!entry_length->entry_length_handler) { return -1; } return entry_length->entry_length_handler(entry); } LIBPLDM_ABI_STABLE size_t pldm_bios_table_attr_value_entry_length( const struct pldm_bios_attr_val_table_entry *entry) { return attr_value_table_entry_length(entry); } LIBPLDM_ABI_STABLE uint16_t pldm_bios_table_attr_value_entry_decode_handle( const struct pldm_bios_attr_val_table_entry *entry) { return le16toh(entry->attr_handle); } static size_t pad_size_get(size_t size_without_pad) { return (4 - (size_without_pad % 4)) % 4; } static uint8_t *pad_append(uint8_t *table_end, size_t pad_size) { while (pad_size--) { *table_end++ = 0; } return table_end; } static uint8_t *checksum_append(uint8_t *table_end, uint32_t checksum) { checksum = htole32(checksum); memcpy(table_end, &checksum, sizeof(checksum)); return table_end + sizeof(checksum); } LIBPLDM_ABI_STABLE size_t pldm_bios_table_pad_checksum_size(size_t size_without_pad) { return pad_size_get(size_without_pad) + sizeof(uint32_t); } LIBPLDM_ABI_STABLE int pldm_bios_table_append_pad_checksum(void *table, size_t capacity, size_t *size) { if (!table || !size) { return PLDM_ERROR_INVALID_DATA; } size_t pad_checksum_size = pldm_bios_table_pad_checksum_size(*size); if (SIZE_MAX - pad_checksum_size < *size) { return PLDM_ERROR_INVALID_LENGTH; } size_t total_length = *size + pad_checksum_size; if (capacity < total_length) { return PLDM_ERROR_INVALID_LENGTH; } if (UINTPTR_MAX - *size < (uintptr_t)table) { return PLDM_ERROR_INVALID_LENGTH; } uint8_t *table_end = (uint8_t *)table + *size; size_t pad_size = pad_size_get(*size); table_end = pad_append(table_end, pad_size); uint32_t checksum = crc32(table, *size + pad_size); checksum_append(table_end, checksum); *size = total_length; return PLDM_SUCCESS; } struct pldm_bios_table_iter { const uint8_t *table_data; size_t table_len; size_t current_pos; ssize_t (*entry_length_handler)(const void *table_entry); }; LIBPLDM_ABI_DEPRECATED_UNSAFE struct pldm_bios_table_iter * pldm_bios_table_iter_create(const void *table, size_t length, enum pldm_bios_table_types type) { struct pldm_bios_table_iter *iter = malloc(sizeof(*iter)); assert(iter != NULL); if (!iter) { return NULL; } iter->table_data = table; iter->table_len = length; iter->current_pos = 0; iter->entry_length_handler = NULL; switch (type) { case PLDM_BIOS_STRING_TABLE: iter->entry_length_handler = string_table_entry_length; break; case PLDM_BIOS_ATTR_TABLE: iter->entry_length_handler = attr_table_entry_length; break; case PLDM_BIOS_ATTR_VAL_TABLE: iter->entry_length_handler = attr_value_table_entry_length; break; } return iter; } LIBPLDM_ABI_STABLE void pldm_bios_table_iter_free(struct pldm_bios_table_iter *iter) { free(iter); } #define pad_and_check_max 7 LIBPLDM_ABI_DEPRECATED_UNSAFE bool pldm_bios_table_iter_is_end(const struct pldm_bios_table_iter *iter) { ssize_t len; if (!iter) { return true; } if (iter->current_pos > iter->table_len) { return true; } if (iter->table_len - iter->current_pos <= pad_and_check_max) { return true; } len = iter->entry_length_handler(iter->table_data + iter->current_pos); return len < 0; } LIBPLDM_ABI_STABLE void pldm_bios_table_iter_next(struct pldm_bios_table_iter *iter) { if (pldm_bios_table_iter_is_end(iter)) { return; } const void *entry = iter->table_data + iter->current_pos; ssize_t rc = iter->entry_length_handler(entry); /* Prevent bad behaviour by acting as if we've hit the end of the iterator */ if (rc < 0) { return; } iter->current_pos += rc; } LIBPLDM_ABI_STABLE const void *pldm_bios_table_iter_value(struct pldm_bios_table_iter *iter) { return iter->table_data + iter->current_pos; } typedef bool (*equal_handler)(const void *entry, const void *key); static const void * pldm_bios_table_entry_find_by_iter(struct pldm_bios_table_iter *iter, const void *key, equal_handler equal) { const void *entry; while (!pldm_bios_table_iter_is_end(iter)) { entry = pldm_bios_table_iter_value(iter); if (equal(entry, key)) { return entry; } pldm_bios_table_iter_next(iter); } return NULL; } static const void * pldm_bios_table_entry_find_from_table(const void *table, size_t length, enum pldm_bios_table_types type, equal_handler equal, const void *key) { struct pldm_bios_table_iter *iter = pldm_bios_table_iter_create(table, length, type); const void *entry = pldm_bios_table_entry_find_by_iter(iter, key, equal); pldm_bios_table_iter_free(iter); return entry; } static bool string_table_handle_equal(const void *entry, const void *key) { const struct pldm_bios_string_table_entry *string_entry = entry; uint16_t handle = *(uint16_t *)key; if (pldm_bios_table_string_entry_decode_handle(string_entry) == handle) { return true; } return false; } LIBPLDM_ABI_DEPRECATED_UNSAFE const struct pldm_bios_string_table_entry * pldm_bios_table_string_find_by_handle(const void *table, size_t length, uint16_t handle) { return pldm_bios_table_entry_find_from_table(table, length, PLDM_BIOS_STRING_TABLE, string_table_handle_equal, &handle); } struct string_equal_arg { uint16_t str_length; const char *str; }; static bool string_table_string_equal(const void *entry, const void *key) { const struct pldm_bios_string_table_entry *string_entry = entry; const struct string_equal_arg *arg = key; if (arg->str_length != pldm_bios_table_string_entry_decode_string_length(string_entry)) { return false; } if (memcmp(string_entry->name, arg->str, arg->str_length) != 0) { return false; } return true; } LIBPLDM_ABI_DEPRECATED_UNSAFE const struct pldm_bios_string_table_entry * pldm_bios_table_string_find_by_string(const void *table, size_t length, const char *str) { uint16_t str_length = strlen(str); struct string_equal_arg arg = { str_length, str }; return pldm_bios_table_entry_find_from_table(table, length, PLDM_BIOS_STRING_TABLE, string_table_string_equal, &arg); } static bool attr_table_handle_equal(const void *entry, const void *key) { uint16_t handle = *(uint16_t *)key; return pldm_bios_table_attr_entry_decode_attribute_handle(entry) == handle; } LIBPLDM_ABI_DEPRECATED_UNSAFE const struct pldm_bios_attr_table_entry * pldm_bios_table_attr_find_by_handle(const void *table, size_t length, uint16_t handle) { return pldm_bios_table_entry_find_from_table(table, length, PLDM_BIOS_ATTR_TABLE, attr_table_handle_equal, &handle); } static bool attr_table_string_handle_equal(const void *entry, const void *key) { uint16_t handle = *(uint16_t *)key; return pldm_bios_table_attr_entry_decode_string_handle(entry) == handle; } LIBPLDM_ABI_DEPRECATED_UNSAFE const struct pldm_bios_attr_table_entry * pldm_bios_table_attr_find_by_string_handle(const void *table, size_t length, uint16_t handle) { return pldm_bios_table_entry_find_from_table( table, length, PLDM_BIOS_ATTR_TABLE, attr_table_string_handle_equal, &handle); } static bool attr_value_table_handle_equal(const void *entry, const void *key) { uint16_t handle = *(uint16_t *)key; return pldm_bios_table_attr_value_entry_decode_handle(entry) == handle; } LIBPLDM_ABI_DEPRECATED_UNSAFE const struct pldm_bios_attr_val_table_entry * pldm_bios_table_attr_value_find_by_handle(const void *table, size_t length, uint16_t handle) { return pldm_bios_table_entry_find_from_table( table, length, PLDM_BIOS_ATTR_VAL_TABLE, attr_value_table_handle_equal, &handle); } LIBPLDM_ABI_STABLE int pldm_bios_table_attr_value_copy_and_update( const void *src_table, size_t src_length, void *dest_table, size_t *dest_length, const void *entry, size_t entry_length) { struct pldm_bios_table_iter *iter = pldm_bios_table_iter_create( src_table, src_length, PLDM_BIOS_ATTR_VAL_TABLE); int rc = PLDM_SUCCESS; const struct pldm_bios_attr_val_table_entry *tmp; const struct pldm_bios_attr_val_table_entry *to_update = entry; size_t buffer_length = *dest_length; size_t copied_length = 0; size_t length = 0; while (!pldm_bios_table_iter_is_end(iter)) { tmp = pldm_bios_table_iter_attr_value_entry_value(iter); length = attr_value_table_entry_length(tmp); /* we need the tmp's entry_length here, iter_next will calculate * it too, use current_pos directly to avoid calculating it * twice */ iter->current_pos += length; if (tmp->attr_handle == to_update->attr_handle) { if (tmp->attr_type != to_update->attr_type) { rc = PLDM_ERROR_INVALID_DATA; goto out; } length = entry_length; tmp = entry; } if (copied_length + length > buffer_length) { rc = PLDM_ERROR_INVALID_LENGTH; goto out; } memcpy((uint8_t *)dest_table + copied_length, tmp, length); copied_length += length; } size_t pad_checksum_size = pldm_bios_table_pad_checksum_size(copied_length); if ((pad_checksum_size + copied_length) > buffer_length) { rc = PLDM_ERROR_INVALID_LENGTH; goto out; } rc = pldm_bios_table_append_pad_checksum(dest_table, buffer_length, &copied_length); if (rc == PLDM_SUCCESS) { *dest_length = copied_length; } out: pldm_bios_table_iter_free(iter); return rc; } LIBPLDM_ABI_STABLE bool pldm_bios_table_checksum(const uint8_t *table, size_t size) { if (table == NULL) { return false; } // 12: BIOSStringHandle(uint16) + BIOSStringLength(uint16) + // Variable(4) + checksum(uint32) if (size < 12) { return false; } uint32_t src_crc = le32toh(*(uint32_t *)(table + size - 4)); uint32_t dst_crc = crc32(table, size - 4); return src_crc == dst_crc; }