| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #define IN_LIBXML |
| #include "libxml.h" |
|
|
| #include <errno.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <string.h> |
|
|
| #include "private/dict.h" |
| #include "private/error.h" |
| #include "private/globals.h" |
| #include "private/threads.h" |
|
|
| #include <libxml/parser.h> |
| #include <libxml/dict.h> |
| #include <libxml/xmlmemory.h> |
| #include <libxml/xmlstring.h> |
|
|
| #ifndef SIZE_MAX |
| #define SIZE_MAX ((size_t) -1) |
| #endif |
|
|
| #define MAX_FILL_NUM 7 |
| #define MAX_FILL_DENOM 8 |
| #define MIN_HASH_SIZE 8 |
| #define MAX_HASH_SIZE (1u << 31) |
|
|
| typedef struct _xmlDictStrings xmlDictStrings; |
| typedef xmlDictStrings *xmlDictStringsPtr; |
| struct _xmlDictStrings { |
| xmlDictStringsPtr next; |
| xmlChar *free; |
| xmlChar *end; |
| size_t size; |
| size_t nbStrings; |
| xmlChar array[1]; |
| }; |
|
|
| typedef xmlHashedString xmlDictEntry; |
|
|
| |
| |
| |
| struct _xmlDict { |
| int ref_counter; |
|
|
| xmlDictEntry *table; |
| size_t size; |
| unsigned int nbElems; |
| xmlDictStringsPtr strings; |
|
|
| struct _xmlDict *subdict; |
| |
| unsigned seed; |
| |
| size_t limit; |
| }; |
|
|
| |
| |
| |
| |
| static xmlMutex xmlDictMutex; |
|
|
| |
| |
| |
| |
| |
| int |
| xmlInitializeDict(void) { |
| xmlInitParser(); |
| return(0); |
| } |
|
|
| |
| |
| |
| void |
| xmlInitDictInternal(void) { |
| xmlInitMutex(&xmlDictMutex); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| void |
| xmlDictCleanup(void) { |
| } |
|
|
| |
| |
| |
| void |
| xmlCleanupDictInternal(void) { |
| xmlCleanupMutex(&xmlDictMutex); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static const xmlChar * |
| xmlDictAddString(xmlDictPtr dict, const xmlChar *name, unsigned int namelen) { |
| xmlDictStringsPtr pool; |
| const xmlChar *ret; |
| size_t size = 0; |
| size_t limit = 0; |
|
|
| pool = dict->strings; |
| while (pool != NULL) { |
| if ((size_t)(pool->end - pool->free) > namelen) |
| goto found_pool; |
| if (pool->size > size) size = pool->size; |
| limit += pool->size; |
| pool = pool->next; |
| } |
| |
| |
| |
| if (pool == NULL) { |
| if ((dict->limit > 0) && (limit > dict->limit)) { |
| return(NULL); |
| } |
|
|
| if (size == 0) { |
| size = 1000; |
| } else { |
| if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4) |
| size *= 4; |
| else |
| size = SIZE_MAX - sizeof(xmlDictStrings); |
| } |
| if (size / 4 < namelen) { |
| if ((size_t) namelen + 0 < (SIZE_MAX - sizeof(xmlDictStrings)) / 4) |
| size = 4 * (size_t) namelen; |
| else |
| return(NULL); |
| } |
| pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size); |
| if (pool == NULL) |
| return(NULL); |
| pool->size = size; |
| pool->nbStrings = 0; |
| pool->free = &pool->array[0]; |
| pool->end = &pool->array[size]; |
| pool->next = dict->strings; |
| dict->strings = pool; |
| } |
| found_pool: |
| ret = pool->free; |
| memcpy(pool->free, name, namelen); |
| pool->free += namelen; |
| *(pool->free++) = 0; |
| pool->nbStrings++; |
| return(ret); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static const xmlChar * |
| xmlDictAddQString(xmlDictPtr dict, const xmlChar *prefix, unsigned int plen, |
| const xmlChar *name, unsigned int namelen) |
| { |
| xmlDictStringsPtr pool; |
| const xmlChar *ret; |
| size_t size = 0; |
| size_t limit = 0; |
|
|
| pool = dict->strings; |
| while (pool != NULL) { |
| if ((size_t)(pool->end - pool->free) > namelen + plen + 1) |
| goto found_pool; |
| if (pool->size > size) size = pool->size; |
| limit += pool->size; |
| pool = pool->next; |
| } |
| |
| |
| |
| if (pool == NULL) { |
| if ((dict->limit > 0) && (limit > dict->limit)) { |
| return(NULL); |
| } |
|
|
| if (size == 0) size = 1000; |
| else size *= 4; |
| if (size < 4 * (namelen + plen + 1)) |
| size = 4 * (namelen + plen + 1); |
| pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size); |
| if (pool == NULL) |
| return(NULL); |
| pool->size = size; |
| pool->nbStrings = 0; |
| pool->free = &pool->array[0]; |
| pool->end = &pool->array[size]; |
| pool->next = dict->strings; |
| dict->strings = pool; |
| } |
| found_pool: |
| ret = pool->free; |
| memcpy(pool->free, prefix, plen); |
| pool->free += plen; |
| *(pool->free++) = ':'; |
| memcpy(pool->free, name, namelen); |
| pool->free += namelen; |
| *(pool->free++) = 0; |
| pool->nbStrings++; |
| return(ret); |
| } |
|
|
| |
| |
| |
| |
| |
| xmlDict * |
| xmlDictCreate(void) { |
| xmlDictPtr dict; |
|
|
| xmlInitParser(); |
|
|
| dict = xmlMalloc(sizeof(xmlDict)); |
| if (dict == NULL) |
| return(NULL); |
| dict->ref_counter = 1; |
| dict->limit = 0; |
|
|
| dict->size = 0; |
| dict->nbElems = 0; |
| dict->table = NULL; |
| dict->strings = NULL; |
| dict->subdict = NULL; |
| dict->seed = xmlRandom(); |
| #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
| dict->seed = 0; |
| #endif |
| return(dict); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| xmlDict * |
| xmlDictCreateSub(xmlDict *sub) { |
| xmlDictPtr dict = xmlDictCreate(); |
|
|
| if ((dict != NULL) && (sub != NULL)) { |
| dict->seed = sub->seed; |
| dict->subdict = sub; |
| xmlDictReference(dict->subdict); |
| } |
| return(dict); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| int |
| xmlDictReference(xmlDict *dict) { |
| if (dict == NULL) return -1; |
| xmlMutexLock(&xmlDictMutex); |
| dict->ref_counter++; |
| xmlMutexUnlock(&xmlDictMutex); |
| return(0); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| void |
| xmlDictFree(xmlDict *dict) { |
| xmlDictStringsPtr pool, nextp; |
|
|
| if (dict == NULL) |
| return; |
|
|
| |
| xmlMutexLock(&xmlDictMutex); |
| dict->ref_counter--; |
| if (dict->ref_counter > 0) { |
| xmlMutexUnlock(&xmlDictMutex); |
| return; |
| } |
|
|
| xmlMutexUnlock(&xmlDictMutex); |
|
|
| if (dict->subdict != NULL) { |
| xmlDictFree(dict->subdict); |
| } |
|
|
| if (dict->table) { |
| xmlFree(dict->table); |
| } |
| pool = dict->strings; |
| while (pool != NULL) { |
| nextp = pool->next; |
| xmlFree(pool); |
| pool = nextp; |
| } |
| xmlFree(dict); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| int |
| xmlDictOwns(xmlDict *dict, const xmlChar *str) { |
| xmlDictStringsPtr pool; |
|
|
| if ((dict == NULL) || (str == NULL)) |
| return(-1); |
| pool = dict->strings; |
| while (pool != NULL) { |
| if ((str >= &pool->array[0]) && (str <= pool->free)) |
| return(1); |
| pool = pool->next; |
| } |
| if (dict->subdict) |
| return(xmlDictOwns(dict->subdict, str)); |
| return(0); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| int |
| xmlDictSize(xmlDict *dict) { |
| if (dict == NULL) |
| return(-1); |
| if (dict->subdict) |
| return(dict->nbElems + dict->subdict->nbElems); |
| return(dict->nbElems); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| size_t |
| xmlDictSetLimit(xmlDict *dict, size_t limit) { |
| size_t ret; |
|
|
| if (dict == NULL) |
| return(0); |
| ret = dict->limit; |
| dict->limit = limit; |
| return(ret); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| size_t |
| xmlDictGetUsage(xmlDict *dict) { |
| xmlDictStringsPtr pool; |
| size_t limit = 0; |
|
|
| if (dict == NULL) |
| return(0); |
| pool = dict->strings; |
| while (pool != NULL) { |
| limit += pool->size; |
| pool = pool->next; |
| } |
| return(limit); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| ATTRIBUTE_NO_SANITIZE_INTEGER |
| static unsigned |
| xmlDictHashName(unsigned seed, const xmlChar* data, size_t maxLen, |
| size_t *plen) { |
| unsigned h1, h2; |
| size_t i; |
|
|
| HASH_INIT(h1, h2, seed); |
|
|
| for (i = 0; i < maxLen && data[i]; i++) { |
| HASH_UPDATE(h1, h2, data[i]); |
| } |
|
|
| HASH_FINISH(h1, h2); |
|
|
| *plen = i; |
| return(h2 | MAX_HASH_SIZE); |
| } |
|
|
| ATTRIBUTE_NO_SANITIZE_INTEGER |
| static unsigned |
| xmlDictHashQName(unsigned seed, const xmlChar *prefix, const xmlChar *name, |
| size_t *pplen, size_t *plen) { |
| unsigned h1, h2; |
| size_t i; |
|
|
| HASH_INIT(h1, h2, seed); |
|
|
| for (i = 0; prefix[i] != 0; i++) { |
| HASH_UPDATE(h1, h2, prefix[i]); |
| } |
| *pplen = i; |
|
|
| HASH_UPDATE(h1, h2, ':'); |
|
|
| for (i = 0; name[i] != 0; i++) { |
| HASH_UPDATE(h1, h2, name[i]); |
| } |
| *plen = i; |
|
|
| HASH_FINISH(h1, h2); |
|
|
| |
| |
| |
| |
| return(h2 | MAX_HASH_SIZE); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| unsigned |
| xmlDictComputeHash(const xmlDict *dict, const xmlChar *string) { |
| size_t len; |
| return(xmlDictHashName(dict->seed, string, SIZE_MAX, &len)); |
| } |
|
|
| #define HASH_ROL31(x,n) ((x) << (n) | ((x) & 0x7FFFFFFF) >> (31 - (n))) |
|
|
| |
| |
| |
| |
| |
| |
| |
| ATTRIBUTE_NO_SANITIZE_INTEGER |
| unsigned |
| xmlDictCombineHash(unsigned v1, unsigned v2) { |
| |
| |
| |
| |
| v1 ^= v2; |
| v1 += HASH_ROL31(v2, 5); |
|
|
| return((v1 & 0xFFFFFFFF) | 0x80000000); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| ATTRIBUTE_NO_SANITIZE_INTEGER |
| static xmlDictEntry * |
| xmlDictFindEntry(const xmlDict *dict, const xmlChar *prefix, |
| const xmlChar *name, int len, unsigned hashValue, |
| int *pfound) { |
| xmlDictEntry *entry; |
| unsigned mask, pos, displ; |
| int found = 0; |
|
|
| mask = dict->size - 1; |
| pos = hashValue & mask; |
| entry = &dict->table[pos]; |
|
|
| if (entry->hashValue != 0) { |
| |
| |
| |
| |
| |
| displ = 0; |
|
|
| do { |
| if (entry->hashValue == hashValue) { |
| if (prefix == NULL) { |
| |
| |
| |
| if ((strncmp((const char *) entry->name, |
| (const char *) name, len) == 0) && |
| (entry->name[len] == 0)) { |
| found = 1; |
| break; |
| } |
| } else { |
| if (xmlStrQEqual(prefix, name, entry->name)) { |
| found = 1; |
| break; |
| } |
| } |
| } |
|
|
| displ++; |
| pos++; |
| entry++; |
| if ((pos & mask) == 0) |
| entry = dict->table; |
| } while ((entry->hashValue != 0) && |
| (((pos - entry->hashValue) & mask) >= displ)); |
| } |
|
|
| *pfound = found; |
| return(entry); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| static int |
| xmlDictGrow(xmlDictPtr dict, unsigned size) { |
| const xmlDictEntry *oldentry, *oldend, *end; |
| xmlDictEntry *table; |
| unsigned oldsize, i; |
|
|
| |
| if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0])) |
| return(-1); |
| table = xmlMalloc(size * sizeof(table[0])); |
| if (table == NULL) |
| return(-1); |
| memset(table, 0, size * sizeof(table[0])); |
|
|
| oldsize = dict->size; |
| if (oldsize == 0) |
| goto done; |
|
|
| oldend = &dict->table[oldsize]; |
| end = &table[size]; |
|
|
| |
| |
| |
| |
| |
| |
| |
| oldentry = dict->table; |
| while (oldentry->hashValue != 0) { |
| if (++oldentry >= oldend) |
| oldentry = dict->table; |
| } |
|
|
| for (i = 0; i < oldsize; i++) { |
| if (oldentry->hashValue != 0) { |
| xmlDictEntry *entry = &table[oldentry->hashValue & (size - 1)]; |
|
|
| while (entry->hashValue != 0) { |
| if (++entry >= end) |
| entry = table; |
| } |
| *entry = *oldentry; |
| } |
|
|
| if (++oldentry >= oldend) |
| oldentry = dict->table; |
| } |
|
|
| xmlFree(dict->table); |
|
|
| done: |
| dict->table = table; |
| dict->size = size; |
|
|
| return(0); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| ATTRIBUTE_NO_SANITIZE_INTEGER |
| static const xmlDictEntry * |
| xmlDictLookupInternal(xmlDict *dict, const xmlChar *prefix, |
| const xmlChar *name, int maybeLen, int update) { |
| xmlDictEntry *entry = NULL; |
| const xmlChar *ret; |
| unsigned hashValue, newSize; |
| size_t maxLen, len, plen, klen; |
| int found = 0; |
|
|
| if ((dict == NULL) || (name == NULL)) |
| return(NULL); |
|
|
| maxLen = (maybeLen < 0) ? SIZE_MAX : (size_t) maybeLen; |
|
|
| if (prefix == NULL) { |
| hashValue = xmlDictHashName(dict->seed, name, maxLen, &len); |
| if (len > INT_MAX / 2) |
| return(NULL); |
| klen = len; |
| } else { |
| hashValue = xmlDictHashQName(dict->seed, prefix, name, &plen, &len); |
| if ((len > INT_MAX / 2) || (plen >= INT_MAX / 2 - len)) |
| return(NULL); |
| klen = plen + 1 + len; |
| } |
|
|
| if ((dict->limit > 0) && (klen >= dict->limit)) |
| return(NULL); |
|
|
| |
| |
| |
| if (dict->size == 0) { |
| newSize = MIN_HASH_SIZE; |
| } else { |
| entry = xmlDictFindEntry(dict, prefix, name, klen, hashValue, &found); |
| if (found) |
| return(entry); |
|
|
| if (dict->nbElems + 1 > dict->size / MAX_FILL_DENOM * MAX_FILL_NUM) { |
| if (dict->size >= MAX_HASH_SIZE) |
| return(NULL); |
| newSize = dict->size * 2; |
| } else { |
| newSize = 0; |
| } |
| } |
|
|
| if ((dict->subdict != NULL) && (dict->subdict->size > 0)) { |
| xmlDictEntry *subEntry; |
| unsigned subHashValue; |
|
|
| if (prefix == NULL) |
| subHashValue = xmlDictHashName(dict->subdict->seed, name, len, |
| &len); |
| else |
| subHashValue = xmlDictHashQName(dict->subdict->seed, prefix, name, |
| &plen, &len); |
| subEntry = xmlDictFindEntry(dict->subdict, prefix, name, klen, |
| subHashValue, &found); |
| if (found) |
| return(subEntry); |
| } |
|
|
| if (!update) |
| return(NULL); |
|
|
| |
| |
| |
| if (newSize > 0) { |
| unsigned mask, displ, pos; |
|
|
| if (xmlDictGrow(dict, newSize) != 0) |
| return(NULL); |
|
|
| |
| |
| |
| mask = dict->size - 1; |
| displ = 0; |
| pos = hashValue & mask; |
| entry = &dict->table[pos]; |
|
|
| while ((entry->hashValue != 0) && |
| ((pos - entry->hashValue) & mask) >= displ) { |
| displ++; |
| pos++; |
| entry++; |
| if ((pos & mask) == 0) |
| entry = dict->table; |
| } |
| } |
|
|
| if (prefix == NULL) |
| ret = xmlDictAddString(dict, name, len); |
| else |
| ret = xmlDictAddQString(dict, prefix, plen, name, len); |
| if (ret == NULL) |
| return(NULL); |
|
|
| |
| |
| |
| if (entry->hashValue != 0) { |
| const xmlDictEntry *end = &dict->table[dict->size]; |
| const xmlDictEntry *cur = entry; |
|
|
| do { |
| cur++; |
| if (cur >= end) |
| cur = dict->table; |
| } while (cur->hashValue != 0); |
|
|
| if (cur < entry) { |
| |
| |
| |
| |
| memmove(&dict->table[1], dict->table, |
| (char *) cur - (char *) dict->table); |
| cur = end - 1; |
| dict->table[0] = *cur; |
| } |
|
|
| memmove(&entry[1], entry, (char *) cur - (char *) entry); |
| } |
|
|
| |
| |
| |
| entry->hashValue = hashValue; |
| entry->name = ret; |
|
|
| dict->nbElems++; |
|
|
| return(entry); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const xmlChar * |
| xmlDictLookup(xmlDict *dict, const xmlChar *name, int len) { |
| const xmlDictEntry *entry; |
|
|
| entry = xmlDictLookupInternal(dict, NULL, name, len, 1); |
| if (entry == NULL) |
| return(NULL); |
| return(entry->name); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| xmlHashedString |
| xmlDictLookupHashed(xmlDict *dict, const xmlChar *name, int len) { |
| const xmlDictEntry *entry; |
| xmlHashedString ret; |
|
|
| entry = xmlDictLookupInternal(dict, NULL, name, len, 1); |
|
|
| if (entry == NULL) { |
| ret.name = NULL; |
| ret.hashValue = 0; |
| } else { |
| ret = *entry; |
| } |
|
|
| return(ret); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| const xmlChar * |
| xmlDictExists(xmlDict *dict, const xmlChar *name, int len) { |
| const xmlDictEntry *entry; |
|
|
| entry = xmlDictLookupInternal(dict, NULL, name, len, 0); |
| if (entry == NULL) |
| return(NULL); |
| return(entry->name); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const xmlChar * |
| xmlDictQLookup(xmlDict *dict, const xmlChar *prefix, const xmlChar *name) { |
| const xmlDictEntry *entry; |
|
|
| entry = xmlDictLookupInternal(dict, prefix, name, -1, 1); |
| if (entry == NULL) |
| return(NULL); |
| return(entry->name); |
| } |
|
|
| |
| |
| |
|
|
| #ifdef _WIN32 |
| #define WIN32_LEAN_AND_MEAN |
| #include <windows.h> |
| #include <bcrypt.h> |
| #else |
| #if HAVE_DECL_GETENTROPY |
| |
| #include <unistd.h> |
| |
| #include <sys/random.h> |
| #endif |
| #include <time.h> |
| #endif |
|
|
| static xmlMutex xmlRngMutex; |
|
|
| static unsigned globalRngState[2]; |
|
|
| |
| |
| |
| |
| ATTRIBUTE_NO_SANITIZE_INTEGER |
| void |
| xmlInitRandom(void) { |
| xmlInitMutex(&xmlRngMutex); |
|
|
| { |
| #ifdef _WIN32 |
| NTSTATUS status; |
|
|
| |
| |
| |
| |
| |
| |
| |
| status = BCryptGenRandom(NULL, (unsigned char *) globalRngState, |
| sizeof(globalRngState), |
| BCRYPT_USE_SYSTEM_PREFERRED_RNG); |
| if (!BCRYPT_SUCCESS(status)) |
| xmlAbort("libxml2: BCryptGenRandom failed with error code %lu\n", |
| GetLastError()); |
| #else |
| int var; |
|
|
| #if HAVE_DECL_GETENTROPY |
| while (1) { |
| if (getentropy(globalRngState, sizeof(globalRngState)) == 0) |
| return; |
|
|
| |
| |
| |
| |
| |
| |
| if (errno == ENOSYS) |
| break; |
|
|
| |
| |
| |
| |
| |
| if (errno != EINTR) |
| xmlAbort("libxml2: getentropy failed with error code %d\n", |
| errno); |
| } |
| #endif |
|
|
| |
| |
| |
| globalRngState[0] = |
| (unsigned) time(NULL) ^ |
| HASH_ROL((unsigned) ((size_t) &xmlInitRandom & 0xFFFFFFFF), 8); |
| globalRngState[1] = |
| HASH_ROL((unsigned) ((size_t) &xmlRngMutex & 0xFFFFFFFF), 16) ^ |
| HASH_ROL((unsigned) ((size_t) &var & 0xFFFFFFFF), 24); |
| #endif |
| } |
| } |
|
|
| |
| |
| |
| |
| void |
| xmlCleanupRandom(void) { |
| xmlCleanupMutex(&xmlRngMutex); |
| } |
|
|
| ATTRIBUTE_NO_SANITIZE_INTEGER |
| static unsigned |
| xoroshiro64ss(unsigned *s) { |
| unsigned s0 = s[0]; |
| unsigned s1 = s[1]; |
| unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5; |
|
|
| s1 ^= s0; |
| s[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9); |
| s[1] = HASH_ROL(s1, 13); |
|
|
| return(result & 0xFFFFFFFF); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| unsigned |
| xmlGlobalRandom(void) { |
| unsigned ret; |
|
|
| xmlMutexLock(&xmlRngMutex); |
| ret = xoroshiro64ss(globalRngState); |
| xmlMutexUnlock(&xmlRngMutex); |
|
|
| return(ret); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| unsigned |
| xmlRandom(void) { |
| return(xoroshiro64ss(xmlGetLocalRngState())); |
| } |
|
|
|
|