/* $OpenBSD: crypto_ex_data.c,v 1.4 2024/08/03 07:45:26 tb Exp $ */ /* * Copyright (c) 2023 Joel Sing * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #define CRYPTO_EX_DATA_MAX_INDEX 32 struct crypto_ex_data { int class_index; void **slots; size_t slots_len; }; struct crypto_ex_data_index { CRYPTO_EX_new *new_func; CRYPTO_EX_dup *dup_func; CRYPTO_EX_free *free_func; long argl; void *argp; }; struct crypto_ex_data_class { struct crypto_ex_data_index **indexes; size_t indexes_len; size_t next_index; }; static struct crypto_ex_data_class **classes; static int crypto_ex_data_classes_init(void) { struct crypto_ex_data_class **classes_new = NULL; if (classes != NULL) return 1; if ((classes_new = calloc(CRYPTO_EX_INDEX__COUNT, sizeof(struct crypto_ex_data_index))) == NULL) return 0; CRYPTO_w_lock(CRYPTO_LOCK_EX_DATA); if (classes == NULL) { classes = classes_new; classes_new = NULL; } CRYPTO_w_unlock(CRYPTO_LOCK_EX_DATA); free(classes_new); return 1; } static struct crypto_ex_data_class * crypto_ex_data_class_lookup(int class_index) { struct crypto_ex_data_class *class; if (classes == NULL) return NULL; if (class_index < 0 || class_index >= CRYPTO_EX_INDEX__COUNT) return NULL; CRYPTO_r_lock(CRYPTO_LOCK_EX_DATA); class = classes[class_index]; CRYPTO_r_unlock(CRYPTO_LOCK_EX_DATA); return class; } int CRYPTO_get_ex_new_index(int class_index, long argl, void *argp, CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func) { struct crypto_ex_data_class *new_class = NULL; struct crypto_ex_data_index *index = NULL; struct crypto_ex_data_class *class; int idx = -1; if (!crypto_ex_data_classes_init()) goto err; if (class_index < 0 || class_index >= CRYPTO_EX_INDEX__COUNT) goto err; if ((class = classes[class_index]) == NULL) { if ((new_class = calloc(1, sizeof(struct crypto_ex_data_class))) == NULL) goto err; if ((new_class->indexes = calloc(CRYPTO_EX_DATA_MAX_INDEX, sizeof(struct crypto_ex_data_index *))) == NULL) goto err; new_class->indexes_len = CRYPTO_EX_DATA_MAX_INDEX; new_class->next_index = 1; CRYPTO_w_lock(CRYPTO_LOCK_EX_DATA); if (classes[class_index] == NULL) { classes[class_index] = new_class; new_class = NULL; } CRYPTO_w_unlock(CRYPTO_LOCK_EX_DATA); class = classes[class_index]; } if ((index = calloc(1, sizeof(struct crypto_ex_data_index))) == NULL) goto err; index->new_func = new_func; index->dup_func = dup_func; index->free_func = free_func; index->argl = argl; index->argp = argp; CRYPTO_w_lock(CRYPTO_LOCK_EX_DATA); if (class->next_index < class->indexes_len) { idx = class->next_index++; class->indexes[idx] = index; index = NULL; } CRYPTO_w_unlock(CRYPTO_LOCK_EX_DATA); err: if (new_class != NULL) { free(new_class->indexes); free(new_class); } free(index); return idx; } LCRYPTO_ALIAS(CRYPTO_get_ex_new_index); void CRYPTO_cleanup_all_ex_data(void) { struct crypto_ex_data_class *class; int i, j; if (classes == NULL) return; for (i = 0; i < CRYPTO_EX_INDEX__COUNT; i++) { if ((class = classes[i]) == NULL) continue; if (class->indexes != NULL) { for (j = 0; j < CRYPTO_EX_DATA_MAX_INDEX; j++) free(class->indexes[j]); free(class->indexes); } free(class); } free(classes); classes = NULL; } LCRYPTO_ALIAS(CRYPTO_cleanup_all_ex_data); static void crypto_ex_data_clear(CRYPTO_EX_DATA *exdata) { struct crypto_ex_data *ced; if (exdata == NULL) return; if ((ced = exdata->sk) != NULL) { freezero(ced->slots, ced->slots_len * sizeof(void *)); freezero(ced, sizeof(*ced)); } exdata->sk = NULL; } static int crypto_ex_data_init(CRYPTO_EX_DATA *exdata) { struct crypto_ex_data *ced = NULL; if (exdata->sk != NULL) goto err; if ((ced = calloc(1, sizeof(struct crypto_ex_data))) == NULL) goto err; ced->class_index = -1; if ((ced->slots = calloc(CRYPTO_EX_DATA_MAX_INDEX, sizeof(void *))) == NULL) goto err; ced->slots_len = CRYPTO_EX_DATA_MAX_INDEX; exdata->sk = ced; return 1; err: if (ced != NULL) { free(ced->slots); free(ced); } crypto_ex_data_clear(exdata); return 0; } int CRYPTO_new_ex_data(int class_index, void *parent, CRYPTO_EX_DATA *exdata) { struct crypto_ex_data_class *class; struct crypto_ex_data_index *index; struct crypto_ex_data *ced; size_t i, last_index; if (!crypto_ex_data_init(exdata)) goto err; if ((ced = exdata->sk) == NULL) goto err; if (!crypto_ex_data_classes_init()) goto err; if ((class = crypto_ex_data_class_lookup(class_index)) == NULL) goto done; ced->class_index = class_index; /* Existing indexes are immutable, we just have to know when to stop. */ CRYPTO_r_lock(CRYPTO_LOCK_EX_DATA); last_index = class->next_index; CRYPTO_r_unlock(CRYPTO_LOCK_EX_DATA); for (i = 0; i < last_index; i++) { if ((index = class->indexes[i]) == NULL) continue; if (index->new_func == NULL) continue; if (!index->new_func(parent, NULL, exdata, i, index->argl, index->argp)) goto err; } done: return 1; err: CRYPTO_free_ex_data(class_index, parent, exdata); return 0; } LCRYPTO_ALIAS(CRYPTO_new_ex_data); int CRYPTO_dup_ex_data(int class_index, CRYPTO_EX_DATA *dst, CRYPTO_EX_DATA *src) { struct crypto_ex_data *dst_ced, *src_ced; struct crypto_ex_data_class *class; struct crypto_ex_data_index *index; size_t i, last_index; void *val; if (dst == NULL || src == NULL) goto err; /* * Some code calls CRYPTO_new_ex_data() before dup, others never call * CRYPTO_new_ex_data()... so we get to handle both. */ /* XXX - parent == NULL? */ CRYPTO_free_ex_data(class_index, NULL, dst); if (!crypto_ex_data_init(dst)) goto err; if ((dst_ced = dst->sk) == NULL) goto err; if ((src_ced = src->sk) == NULL) goto err; if ((class = crypto_ex_data_class_lookup(class_index)) == NULL) { for (i = 0; i < CRYPTO_EX_DATA_MAX_INDEX; i++) dst_ced->slots[i] = src_ced->slots[i]; goto done; } OPENSSL_assert(src_ced->class_index == class_index); dst_ced->class_index = class_index; /* Existing indexes are immutable, we just have to know when to stop. */ CRYPTO_r_lock(CRYPTO_LOCK_EX_DATA); last_index = class->next_index; CRYPTO_r_unlock(CRYPTO_LOCK_EX_DATA); for (i = 0; i < last_index; i++) { if ((index = class->indexes[i]) == NULL) continue; /* If there is no dup function, we copy the pointer. */ val = src_ced->slots[i]; if (index->dup_func != NULL) { if (!index->dup_func(dst, src, &val, i, index->argl, index->argp)) goto err; } /* If the dup function set data, we will potentially leak. */ if (dst_ced->slots[i] != NULL) goto err; dst_ced->slots[i] = val; } done: return 1; err: /* XXX - parent == NULL? */ CRYPTO_free_ex_data(class_index, NULL, dst); return 0; } LCRYPTO_ALIAS(CRYPTO_dup_ex_data); void CRYPTO_free_ex_data(int class_index, void *parent, CRYPTO_EX_DATA *exdata) { struct crypto_ex_data_class *class; struct crypto_ex_data_index *index; struct crypto_ex_data *ced; size_t i, last_index; if (exdata == NULL) return; if ((ced = exdata->sk) == NULL) goto done; if (ced->class_index == -1) goto done; if ((class = crypto_ex_data_class_lookup(class_index)) == NULL) goto done; OPENSSL_assert(ced->class_index == class_index); /* Existing indexes are immutable, we just have to know when to stop. */ CRYPTO_r_lock(CRYPTO_LOCK_EX_DATA); last_index = class->next_index; CRYPTO_r_unlock(CRYPTO_LOCK_EX_DATA); for (i = 0; i < last_index; i++) { if ((index = class->indexes[i]) == NULL) continue; if (index->free_func == NULL) continue; index->free_func(parent, ced->slots[i], exdata, i, index->argl, index->argp); } done: crypto_ex_data_clear(exdata); } LCRYPTO_ALIAS(CRYPTO_free_ex_data); int CRYPTO_set_ex_data(CRYPTO_EX_DATA *exdata, int idx, void *val) { struct crypto_ex_data *ced; /* * Preserve horrible historical behaviour - allow set to work even if * new has not been called first. */ if ((ced = exdata->sk) == NULL) { if (!crypto_ex_data_init(exdata)) return 0; ced = exdata->sk; } /* XXX - consider preventing set for an unallocated index. */ if (idx < 0 || idx >= ced->slots_len) return 0; ced->slots[idx] = val; return 1; } LCRYPTO_ALIAS(CRYPTO_set_ex_data); void * CRYPTO_get_ex_data(const CRYPTO_EX_DATA *exdata, int idx) { struct crypto_ex_data *ced; if ((ced = exdata->sk) == NULL) return NULL; if (idx < 0 || idx >= ced->slots_len) return NULL; return ced->slots[idx]; } LCRYPTO_ALIAS(CRYPTO_get_ex_data);