ClamAV is an open source (GPLv2) anti-virus toolkit.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
clamav/libclamav/mpool.c

869 lines
18 KiB

/*
* Copyright (C) 2008 Sourcefire, Inc.
*
* Authors: aCaB <acab@clamav.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
/* a naive pool allocator */
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#ifdef USE_MPOOL
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#endif
#if defined(HAVE_MMAP) && defined(HAVE_SYS_MMAN_H)
#include <sys/mman.h>
#endif
#include <stddef.h>
#include "others.h"
#include "str.h"
#include "readdb.h"
/*#define CL_DEBUG*/
#ifdef CL_DEBUG
#include <assert.h>
#define MPOOLMAGIC 0xadde
#define ALLOCPOISON 0x5a
#define FREEPOISON 0xde
#endif
/*#define DEBUGMPOOL
#define EXIT_ON_FLUSH*/
#ifdef DEBUGMPOOL
#define spam(...) cli_warnmsg( __VA_ARGS__)
#else
static inline void spam(const char *fmt, ...) { fmt = fmt; } /* gcc STFU */
#endif
#include "mpool.h"
#undef CL_DEBUG /* bb#2222 */
#define MIN_FRAGSIZE 262144
#if SIZEOF_VOID_P==8
static const unsigned int fragsz[] = {
8,
11,
13,
16,
17,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
37,
40,
41,
48,
56,
72,
74,
75,
76,
78,
79,
80,
81,
101,
104,
109,
113,
116,
120,
128,
131,
143,
151,
152,
153,
196,
256,
360,
403,
404,
432,
486,
514,
548,
578,
604,
633,
697,
743,
784,
839,
1176,
1536,
1666,
2056,
2168,
2392,
2985,
3221,
3433,
3753,
3832,
4104,
4280,
4696,
4952,
5256,
5826,
6264,
7176,
8440,
9096,
16392,
32780,
50961,
63504,
65558,
101912,
131088,
262144,
507976,
524296,
1048584,
2097152,
4194304,
8388608,
/* MAX_ALLOCATION is 184549376 but that's really not need here */
};
#else
15 years ago
static const unsigned int fragsz[] = {
15 years ago
4,
5,
8,
9,
11,
12,
13,
14,
15,
16,
17,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
35,
36,
37,
39,
40,
41,
44,
48,
49,
52,
53,
56,
58,
59,
60,
61,
62,
63,
64,
65,
68,
69,
72,
73,
77,
80,
81,
83,
85,
88,
89,
93,
96,
99,
101,
103,
104,
105,
108,
112,
113,
115,
116,
117,
119,
120,
121,
124,
128,
129,
131,
133,
136,
137,
141,
143,
145,
148,
151,
152,
153,
160,
168,
173,
176,
184,
194,
200,
208,
216,
224,
229,
232,
241,
244,
248,
256,
257,
264,
274,
280,
293,
296,
304,
307,
312,
326,
344,
354,
372,
396,
403,
418,
456,
485,
514,
546,
581,
608,
646,
693,
740,
776,
805,
828,
902,
964,
1028,
1032,
1136,
1238,
1314,
1420,
1501,
1668,
1720,
1832,
1940,
2048,
2119,
2264,
2584,
2724,
2994,
3336,
3428,
3828,
4104,
4471,
4836,
5044,
5176,
5912,
6227,
6792,
7732,
8192,
11272,
12500,
16384,
32768,
63500,
65536,
131080,
253988,
262148,
524292,
1048576,
2097152,
4194304,
8388608,
};
#endif
#define FRAGSBITS (sizeof(fragsz)/sizeof(fragsz[0]))
struct MPMAP {
struct MPMAP *next;
unsigned int size;
unsigned int usize;
};
struct MP {
unsigned int psize;
struct FRAG *avail[FRAGSBITS];
union {
struct MPMAP mpm;
uint64_t dummy_align;
} u;
};
/* alignment of fake handled in the code! */
struct alloced {
uint8_t padding;
uint8_t sbits;
uint8_t fake;
};
struct FRAG {
#ifdef CL_DEBUG
uint16_t magic;
#endif
union {
struct alloced a;
struct unaligned_ptr next;
} u;
};
#define FRAG_OVERHEAD (offsetof(struct FRAG, u.a.fake))
static unsigned int align_to_pagesize(struct MP *mp, unsigned int size) {
return (size / mp->psize + (size % mp->psize != 0)) * mp->psize;
}
static unsigned int to_bits(unsigned int size) {
unsigned int i;
for(i=0; i<FRAGSBITS; i++)
if(fragsz[i] >= size) return i;
return FRAGSBITS;
}
static unsigned int from_bits(unsigned int bits) {
if (bits >= FRAGSBITS) return 0;
return fragsz[bits];
}
static inline unsigned int alignof(unsigned int size)
{
/* conservative estimate of alignment.
* A struct that needs alignment of 'align' is padded by the compiler
* so that sizeof(struct)%align == 0
* (otherwise you wouldn't be able to use it in an array)
* Also align = 2^n.
* Largest alignment we need is 8 bytes (ptr/int64), since we don't use long
* double or __aligned attribute.
* This conservatively estimates that size 32 needs alignment of 8 (even if it might only
* need an alignment of 4).
*/
switch (size%8) {
case 0:
return 8;
case 2:
case 6:
return 2;
case 4:
return 4;
default:
return 1;
}
}
static inline size_t alignto(size_t p, size_t size)
{
/* size is power of 2 */
return (p+size-1)&(~(size-1));
}
struct MP *mpool_create() {
struct MP mp, *mpool_p;
unsigned int sz;
memset(&mp, 0, sizeof(mp));
mp.psize = cli_getpagesize();
sz = align_to_pagesize(&mp, MIN_FRAGSIZE);
mp.u.mpm.usize = sizeof(struct MPMAP);
mp.u.mpm.size = sz - sizeof(mp);
#ifndef _WIN32
if ((mpool_p = (struct MP *)mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE|ANONYMOUS_MAP, -1, 0)) == MAP_FAILED)
#else
if(!(mpool_p = (struct MP *)VirtualAlloc(NULL, sz, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)))
#endif
return NULL;
if (FRAGSBITS > 255) {
cli_errmsg("At most 255 frags possible!\n");
return NULL;
}
if (fragsz[0] < sizeof(void*)) {
cli_errmsg("fragsz[0] too small!\n");
return NULL;
}
#ifdef CL_DEBUG
memset(mpool_p, ALLOCPOISON, sz);
#endif
memcpy(mpool_p, &mp, sizeof(mp));
spam("Map created @%p->%p - size %u out of %u - voidptr=%u\n", mpool_p, (char *)mpool_p + mp.u.mpm.size, mp.u.mpm.usize, mp.u.mpm.size, SIZEOF_VOID_P);
return mpool_p;
}
void mpool_destroy(struct MP *mp) {
struct MPMAP *mpm_next = mp->u.mpm.next, *mpm;
unsigned int mpmsize;
while((mpm = mpm_next)) {
mpmsize = mpm->size;
mpm_next = mpm->next;
#ifdef CL_DEBUG
memset(mpm, FREEPOISON, mpmsize);
#endif
#ifndef _WIN32
munmap((void *)mpm, mpmsize);
#else
VirtualFree(mpm, 0, MEM_RELEASE);
#endif
}
mpmsize = mp->u.mpm.size;
#ifdef CL_DEBUG
memset(mp, FREEPOISON, mpmsize + sizeof(*mp));
#endif
#ifndef _WIN32
munmap((void *)mp, mpmsize + sizeof(*mp));
#else
VirtualFree(mp, 0, MEM_RELEASE);
#endif
spam("Map destroyed @%p\n", mp);
}
void mpool_flush(struct MP *mp) {
size_t used = 0, mused;
struct MPMAP *mpm_next = mp->u.mpm.next, *mpm;
#ifdef EXIT_ON_FLUSH
exit(0);
#endif
while((mpm = mpm_next)) {
mpm_next = mpm->next;
mused = align_to_pagesize(mp, mpm->usize);
if(mused < mpm->size) {
#ifdef CL_DEBUG
memset((char *)mpm + mused, FREEPOISON, mpm->size - mused);
#endif
#ifndef _WIN32
munmap((char *)mpm + mused, mpm->size - mused);
#else
VirtualFree((char *)mpm + mused, mpm->size - mused, MEM_DECOMMIT);
#endif
mpm->size = mused;
}
used += mpm->size;
}
mused = align_to_pagesize(mp, mp->u.mpm.usize + sizeof(*mp));
if (mused < mp->u.mpm.size + sizeof(*mp)) {
#ifdef CL_DEBUG
memset((char *)mp + mused, FREEPOISON, mp->u.mpm.size + sizeof(*mp) - mused);
#endif
#ifndef _WIN32
munmap((char *)mp + mused, mp->u.mpm.size + sizeof(*mp) - mused);
#else
VirtualFree((char *)mp + mused, mp->u.mpm.size + sizeof(*mp) - mused, MEM_DECOMMIT);
#endif
mp->u.mpm.size = mused - sizeof(*mp);
}
used += mp->u.mpm.size;
cli_dbgmsg("pool memory used: %.3f MB\n", used/(1024*1024.0));
spam("Map flushed @%p, in use: %lu\n", mp, used);
}
int mpool_getstats(const struct cl_engine *eng, size_t *used, size_t *total)
{
size_t sum_used = 0, sum_total = 0;
const struct MPMAP *mpm;
const mpool_t *mp;
/* checking refcount is not necessary, but safer */
if (!eng || !eng->refcount)
return -1;
mp = eng->mempool;
if (!mp)
return -1;
for(mpm = &mp->u.mpm; mpm; mpm = mpm->next) {
sum_used += mpm->usize;
sum_total += mpm->size;
}
*used = sum_used;
*total = sum_total;
return 0;
}
static inline unsigned align_increase(unsigned size, unsigned a)
{
/* we must pad with at most a-1 bytes to align start of struct */
return size + a - 1;
}
static void* allocate_aligned(struct MPMAP *mpm, unsigned long size, unsigned align, const char *dbg)
{
/* We could always align the size to maxalign (8), however that wastes
* space.
* So just align the start of each allocation as needed, and then see in
* which sbits bin we fit into.
* Since we are no longer allocating in multiple of 8, we must always
* align the start of each allocation!
*| end of previous allocation | padding | FRAG_OVERHEAD | ptr_aligned |*/
unsigned p = mpm->usize + FRAG_OVERHEAD;
unsigned p_aligned = alignto(p, align);
struct FRAG *f = (struct FRAG*)((char*)mpm + p_aligned - FRAG_OVERHEAD);
unsigned realneed = p_aligned + size - mpm->usize;
unsigned sbits = to_bits(realneed);
unsigned needed = from_bits(sbits);
#ifdef CL_DEBUG
assert(p_aligned + size <= mpm->size);
#endif
f->u.a.sbits = sbits;
f->u.a.padding = p_aligned - p;
mpm->usize += needed;
#ifdef CL_DEBUG
assert(mpm->usize <= mpm->size);
#endif
spam("malloc @%p size %u (%s) origsize %u overhead %u\n", f, realneed, dbg, size, needed - size);
#ifdef CL_DEBUG
f->magic = MPOOLMAGIC;
memset(&f->u.a.fake, ALLOCPOISON, size);
#endif
return &f->u.a.fake;
}
void *mpool_malloc(struct MP *mp, size_t size) {
unsigned align = alignof(size);
unsigned int i, needed = align_increase(size+FRAG_OVERHEAD, align);
const unsigned int sbits = to_bits(needed);
struct FRAG *f = NULL;
struct MPMAP *mpm = &mp->u.mpm;
/* check_all(mp); */
if (!size || sbits == FRAGSBITS) {
cli_errmsg("mpool_malloc(): Attempt to allocate %lu bytes. Please report to http://bugs.clamav.net\n", (unsigned long int) size);
return NULL;
}
/* Case 1: We have a free'd frag */
if((f = mp->avail[sbits])) {
struct FRAG *fold = f;
mp->avail[sbits] = f->u.next.ptr;
/* we always have enough space for this, align_increase ensured that */
f = (struct FRAG*)(alignto((unsigned long)f + FRAG_OVERHEAD, align)-FRAG_OVERHEAD);
f->u.a.sbits = sbits;
f->u.a.padding = (char*)f - (char*)fold;
#ifdef CL_DEBUG
f->magic = MPOOLMAGIC;
memset(&f->u.a.fake, ALLOCPOISON, size);
#endif
spam("malloc @%p size %u (freed) origsize %u overhead %u\n", f, f->u.a.padding + FRAG_OVERHEAD + size, size, needed - size);
return &f->u.a.fake;
}
if (!(needed = from_bits(sbits))) {
cli_errmsg("mpool_malloc(): Attempt to allocate %lu bytes. Please report to http://bugs.clamav.net\n", (unsigned long int) size);
return NULL;
}
/* Case 2: We have nuff room available for this frag already */
while(mpm) {
if(mpm->size - mpm->usize >= needed)
return allocate_aligned(mpm, size, align, "hole");
mpm = mpm->next;
}
/* Case 3: We allocate more */
if (needed + sizeof(*mpm) > MIN_FRAGSIZE)
i = align_to_pagesize(mp, needed + sizeof(*mpm));
else
i = align_to_pagesize(mp, MIN_FRAGSIZE);
#ifndef _WIN32
if ((mpm = (struct MPMAP *)mmap(NULL, i, PROT_READ | PROT_WRITE, MAP_PRIVATE|ANONYMOUS_MAP, -1, 0)) == MAP_FAILED) {
#else
if (!(mpm = (struct MPMAP *)VirtualAlloc(NULL, i, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE))) {
#endif
cli_errmsg("mpool_malloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int)i);
spam("failed to alloc %u bytes (%u requested)\n", i, size);
return NULL;
}
#ifdef CL_DEBUG
memset(mpm, ALLOCPOISON, i);
#endif
mpm->size = i;
mpm->usize = sizeof(*mpm);
mpm->next = mp->u.mpm.next;
mp->u.mpm.next = mpm;
return allocate_aligned(mpm, size, align, "new map");
}
static void *allocbase_fromfrag(struct FRAG *f)
{
#ifdef CL_DEBUG
assert(f->u.a.padding < 8);
#endif
return (char*)f - f->u.a.padding;
}
void mpool_free(struct MP *mp, void *ptr) {
struct FRAG *f = (struct FRAG *)((char *)ptr - FRAG_OVERHEAD);
unsigned int sbits;
if (!ptr) return;
#ifdef CL_DEBUG
assert(f->magic == MPOOLMAGIC && "Attempt to mpool_free a pointer we did not allocate!");
#endif
spam("free @%p\n", f);
sbits = f->u.a.sbits;
f = allocbase_fromfrag(f);
#ifdef CL_DEBUG
memset(f, FREEPOISON, from_bits(sbits));
#endif
f->u.next.ptr = mp->avail[sbits];
mp->avail[sbits] = f;
}
void *mpool_calloc(struct MP *mp, size_t nmemb, size_t size) {
unsigned int needed = nmemb*size;
void *ptr;
if(!needed) return NULL;
if((ptr = mpool_malloc(mp, needed)))
memset(ptr, 0, needed);
return ptr;
}
void *mpool_realloc(struct MP *mp, void *ptr, size_t size) {
struct FRAG *f = (struct FRAG *)((char *)ptr - FRAG_OVERHEAD);
unsigned int csize;
void *new_ptr;
if (!ptr) return mpool_malloc(mp, size);
if(!size || !(csize = from_bits(f->u.a.sbits))) {
cli_errmsg("mpool_realloc(): Attempt to allocate %lu bytes. Please report to http://bugs.clamav.net\n", (unsigned long int) size);
return NULL;
}
csize -= FRAG_OVERHEAD + f->u.a.padding;
if (csize >= size && (!f->u.a.sbits || from_bits(f->u.a.sbits-1)-FRAG_OVERHEAD-f->u.a.padding < size)) {
spam("free @%p\n", f);
spam("malloc @%p size %u (self) origsize %u overhead %u\n", f, size + FRAG_OVERHEAD + f->u.a.padding, size, csize-size+FRAG_OVERHEAD+f->u.a.padding);
return ptr;
}
if (!(new_ptr = mpool_malloc(mp, size)))
return NULL;
memcpy(new_ptr, ptr, csize <= size ? csize : size);
mpool_free(mp, ptr);
return new_ptr;
}
void *mpool_realloc2(struct MP *mp, void *ptr, size_t size) {
void *new_ptr = mpool_realloc(mp, ptr, size);
if(new_ptr)
return new_ptr;
mpool_free(mp, ptr);
return NULL;
}
unsigned char *cli_mpool_hex2str(mpool_t *mp, const char *hex) {
unsigned char *str;
size_t len = strlen((const char*)hex);
if (len&1) {
cli_errmsg("cli_hex2str(): Malformed hexstring: %s (length: %u)\n", hex, (unsigned)len);
return NULL;
}
str = mpool_malloc(mp, (len/2) + 1);
if (cli_hex2str_to(hex, (char*)str, len) == -1) {
mpool_free(mp, str);
return NULL;
}
str[len/2] = '\0';
return str;
}
char *cli_mpool_strdup(mpool_t *mp, const char *s) {
char *alloc;
unsigned int strsz;
if(s == NULL) {
cli_errmsg("cli_mpool_strdup(): s == NULL. Please report to http://bugs.clamav.net\n");
return NULL;
}
strsz = strlen(s) + 1;
alloc = mpool_malloc(mp, strsz);
if(!alloc)
cli_errmsg("cli_mpool_strdup(): Can't allocate memory (%u bytes).\n", (unsigned int) strsz);
else
memcpy(alloc, s, strsz);
return alloc;
}
/* #define EXPAND_PUA */
char *cli_mpool_virname(mpool_t *mp, const char *virname, unsigned int official) {
char *newname, *pt;
#ifdef EXPAND_PUA
char buf[1024];
#endif
if(!virname)
return NULL;
if((pt = strchr(virname, ' ')))
if((pt = strstr(pt, " (Clam)")))
*pt='\0';
if(!virname[0]) {
cli_errmsg("cli_virname: Empty virus name\n");
return NULL;
}
#ifdef EXPAND_PUA
if(!strncmp(virname, "PUA.", 4)) {
snprintf(buf, sizeof(buf), "Possibly-Unwanted-Application(www.clamav.net/support/pua).%s", virname + 4);
buf[sizeof(buf)-1] = '\0';
virname = buf;
}
#endif
if(official)
return cli_mpool_strdup(mp, virname);
newname = (char *)mpool_malloc(mp, strlen(virname) + 11 + 1);
if(!newname) {
cli_errmsg("cli_virname: Can't allocate memory for newname\n");
return NULL;
}
sprintf(newname, "%s.UNOFFICIAL", virname);
return newname;
}
uint16_t *cli_mpool_hex2ui(mpool_t *mp, const char *hex) {
uint16_t *str;
unsigned int len;
len = strlen(hex);
if(len % 2 != 0) {
cli_errmsg("cli_mpool_hex2ui(): Malformed hexstring: %s (length: %u)\n", hex, len);
return NULL;
}
str = mpool_calloc(mp, (len / 2) + 1, sizeof(uint16_t));
if(!str)
return NULL;
if(cli_realhex2ui(hex, str, len))
return str;
mpool_free(mp, str);
return NULL;
}
#ifdef DEBUGMPOOL
void mpool_stats(struct MP *mp) {
unsigned int i=0, ta=0, tu=0;
struct MPMAP *mpm = &mp->u.mpm;
cli_warnmsg("MEMORY POOL STATISTICS\n map \tsize\tused\t%\n");
while(mpm) {
cli_warnmsg("- %u\t%u\t%u\t%f%%\n", i, mpm->size, mpm->usize, (float)mpm->usize/(float)mpm->size*100);
ta+=mpm->size;
tu+=mpm->usize;
i++;
mpm = mpm->next;
}
cli_warnmsg("MEMORY POOL SUMMARY\nMaps: %u\nTotal: %u\nUsed: %u (%f%%)\n", i, ta, tu, (float)tu/(float)ta*100);
}
void check_all(struct MP *mp) {
struct MPMAP *mpm = &mp->u.mpm;
while(mpm) {
volatile unsigned char *c = (unsigned char *)mpm;
unsigned int len = mpm->size;
spam("checking object %p - size %u\n", mpm, len);
while (len--) {
c[len];
}
mpm=mpm->next;
}
}
#endif /* DEBUGMPOOL */
#else
/* dummy definitions to make Solaris linker happy.
* these symbols are declared in libclamav.map */
void mpool_free() {}
void mpool_create() {}
void mpool_destroy() {}
void mpool_getstats() {}
void mpool_calloc() {}
#endif /* USE_MPOOL */