/*
* Soft: Keepalived is a failover program for the LVS project
* <www.linuxvirtualserver.org>. It monitor & manipulate
* a loadbalanced server pool using multi-layer checks.
*
* Part: Memory management framework. This framework is used to
* find any memory leak.
*
* Authors: Alexandre Cassen, <acassen@linux-vs.org>
* Jan Holmberg, <jan@artech.net>
*
* 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.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
*/
#include "config.h"
#ifdef _MEM_CHECK_
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#endif
#include <errno.h>
#include <string.h>
#include "memory.h"
#include "utils.h"
#include "bitops.h"
#include "logger.h"
#include "scheduler.h"
#ifdef _MEM_CHECK_
#include "timer.h"
#include "rbtree.h"
#include "list_head.h"
/* Global var */
size_t mem_allocated; /* Total memory used in Bytes */
static size_t max_mem_allocated; /* Maximum memory used in Bytes */
static const char *terminate_banner; /* banner string for report file */
static bool skip_mem_check_final;
#endif
static void *
xalloc(unsigned long size)
{
void *mem = malloc(size);
if (mem == NULL) {
if (__test_bit(DONT_FORK_BIT, &debug))
perror("Keepalived");
else
log_message(LOG_INFO, "Keepalived xalloc() error - %s", strerror(errno));
exit(KEEPALIVED_EXIT_NO_MEMORY);
}
#ifdef _MEM_CHECK_
mem_allocated += size - sizeof(long);
if (mem_allocated > max_mem_allocated)
max_mem_allocated = mem_allocated;
#endif
return mem;
}
void *
zalloc(unsigned long size)
{
void *mem = xalloc(size);
if (mem)
memset(mem, 0, size);
return mem;
}
/* KeepAlived memory management. in debug mode,
* help finding eventual memory leak.
* Allocation memory types manipulated are :
*
* +-type------------------+-meaning------------------+
* ! FREE_SLOT ! Free slot !
* ! OVERRUN ! Overrun !
* ! FREE_NULL ! free null !
* ! REALLOC_NULL ! realloc null !
* ! DOUBLE_FREE ! double free !
* ! REALLOC_DOUBLE_FREE ! realloc freed block !
* ! FREE_NOT_ALLOC ! Not previously allocated !
* ! REALLOC_NOT_ALLOC ! Not previously allocated !
* ! MALLOC_ZERO_SIZE ! malloc with size 0 !
* ! REALLOC_ZERO_SIZE ! realloc with size 0 !
* ! LAST_FREE ! Last free list !
* ! ALLOCATED ! Allocated !
* +-----------------------+--------------------------+
*
* global variable debug bit MEM_ERR_DETECT_BIT used to
* flag some memory error.
*
*/
#ifdef _MEM_CHECK_
enum slot_type {
FREE_SLOT = 0,
OVERRUN,
FREE_NULL,
REALLOC_NULL,
DOUBLE_FREE,
REALLOC_DOUBLE_FREE,
FREE_NOT_ALLOC,
REALLOC_NOT_ALLOC,
MALLOC_ZERO_SIZE,
REALLOC_ZERO_SIZE,
LAST_FREE,
ALLOCATED,
} ;
#define TIME_STR_LEN 9
#if ULONG_MAX == 0xffffffffffffffffUL
#define CHECK_VAL 0xa5a55a5aa5a55a5aUL
#elif ULONG_MAX == 0xffffffffUL
#define CHECK_VAL 0xa5a55a5aUL
#else
#define CHECK_VAL 0xa5a5
#endif
#define FREE_LIST_SIZE 256
typedef struct {
enum slot_type type;
int line;
const char *func;
const char *file;
void *ptr;
size_t size;
union {
list_head_t l; /* When on free list */
rb_node_t t;
};
unsigned seq_num;
} MEMCHECK;
/* Last free pointers */
static LH_LIST_HEAD(free_list);
static unsigned free_list_size;
/* alloc_list entries used for 1000 VRRP instance each with VMAC interfaces is 33589 */
static rb_root_t alloc_list = RB_ROOT;
static LH_LIST_HEAD(bad_list);
static unsigned number_alloc_list; /* number of alloc_list allocation entries */
static unsigned max_alloc_list;
static unsigned num_mallocs;
static unsigned num_reallocs;
static unsigned seq_num;
static FILE *log_op = NULL;
static inline int
memcheck_ptr_cmp(MEMCHECK *m1, MEMCHECK *m2)
{
return m1->ptr - m2->ptr;
}
static inline int
memcheck_seq_cmp(MEMCHECK *m1, MEMCHECK *m2)
{
return m1->seq_num - m2->seq_num;
}
static const char *
format_time(void)
{
static char time_buf[TIME_STR_LEN+1];
strftime(time_buf, sizeof time_buf, "%T ", localtime(&time_now.tv_sec));
return time_buf;
}
void
memcheck_log(const char *called_func, const char *param, const char *file, const char *function, int line)
{
int len = strlen(called_func) + (param ? strlen(param) : 0);
if ((len = 36 - len) < 0)
len = 0;
fprintf(log_op, "%s%*s%s(%s) at %s, %d, %s\n",
format_time(), len, "", called_func, param ? param : "", file, line, function);
}
static MEMCHECK *
get_free_alloc_entry(void)
{
MEMCHECK *entry;
/* If number on free list < 256, allocate new entry, otherwise take head */
if (free_list_size < 256)
entry = malloc(sizeof *entry);
else {
entry = list_first_entry(&free_list, MEMCHECK, l);
list_head_del(&entry->l);
free_list_size--;
}
entry->seq_num = seq_num++;
return entry;
}
void *
keepalived_malloc(size_t size, const char *file, const char *function, int line)
{
void *buf;
MEMCHECK *entry, *entry2;
buf = zalloc(size + sizeof (unsigned long));
*(unsigned long *) ((char *) buf + size) = size + CHECK_VAL;
entry = get_free_alloc_entry();
entry->ptr = buf;
entry->size = size;
entry->file = file;
entry->func = function;
entry->line = line;
entry->type = ALLOCATED;
rb_insert_sort(&alloc_list, entry, t, memcheck_ptr_cmp);
if (++number_alloc_list > max_alloc_list)
max_alloc_list = number_alloc_list;
fprintf(log_op, "%szalloc [%3d:%3d], %9p, %4zu at %s, %3d, %s%s\n",
format_time(), entry->seq_num, number_alloc_list, buf, size, file, line, function, !size ? " - size is 0" : "");
#ifdef _MEM_CHECK_LOG_
if (__test_bit(MEM_CHECK_LOG_BIT, &debug))
log_message(LOG_INFO, "zalloc[%3d:%3d], %9p, %4zu at %s, %3d, %s",
entry->seq_num, number_alloc_list, buf, size, file, line, function);
#endif
num_mallocs++;
if (!size) {
/* Record malloc with 0 size */
entry2 = get_free_alloc_entry();
*entry2 = *entry;
entry2->type = MALLOC_ZERO_SIZE;
list_add_tail(&entry2->l, &bad_list);
}
return buf;
}
static void *
keepalived_free_realloc_common(void *buffer, size_t size, const char *file, const char *function, int line, bool is_realloc)
{
unsigned long check;
MEMCHECK *entry, *entry2, *le;
MEMCHECK search = {.ptr = buffer};
/* If nullpointer remember */
if (buffer == NULL) {
entry = get_free_alloc_entry();
entry->ptr = NULL;
entry->size = size;
entry->file = file;
entry->func = function;
entry->line = line;
entry->type = !size ? FREE_NULL : REALLOC_NULL;
if (!is_realloc)
fprintf(log_op, "%s%-7s%9s, %9s, %4s at %s, %3d, %s\n", format_time(),
"free", "ERROR", "NULL", "",
file, line, function);
else
fprintf(log_op, "%s%-7s%9s, %9s, %4zu at %s, %3d, %s%s\n", format_time(),
"realloc", "ERROR", "NULL",
size, file, line, function, size ? " *** converted to malloc" : "");
__set_bit(MEM_ERR_DETECT_BIT, &debug);
list_add_tail(&entry->l, &bad_list);
return !size ? NULL : keepalived_malloc(size, file, function, line);
}
entry = rb_search(&alloc_list, &search, t, memcheck_ptr_cmp);
/* Not found */
if (!entry) {
entry = get_free_alloc_entry();
entry->ptr = buffer;
entry->size = size;
entry->file = file;
entry->func = function;
entry->line = line;
entry->type = !size ? FREE_NOT_ALLOC : REALLOC_NOT_ALLOC;
entry->seq_num = seq_num++;
if (!is_realloc)
fprintf(log_op, "%s%-7s%9s, %9p, at %s, %3d, %s - not found\n", format_time(),
"free", "ERROR",
buffer, file, line, function);
else
fprintf(log_op, "%s%-7s%9s, %9p, %4zu at %s, %3d, %s - not found\n", format_time(),
"realloc", "ERROR",
buffer, size, file, line, function);
__set_bit(MEM_ERR_DETECT_BIT, &debug);
list_for_each_entry_reverse(le, &free_list, l) {
if (le->ptr == buffer &&
le->type == LAST_FREE) {
fprintf
(log_op, "%11s-> pointer last released at [%3d:%3d], at %s, %3d, %s\n",
"", le->seq_num, number_alloc_list,
le->file, le->line,
le->func);
entry->type = !size ? DOUBLE_FREE : REALLOC_DOUBLE_FREE;
break;
}
};
list_add_tail(&entry->l, &bad_list);
return NULL;
}
check = entry->size + CHECK_VAL;
if (*(unsigned long *)((char *)buffer + entry->size) != check) {
entry2 = get_free_alloc_entry();
*entry2 = *entry;
entry2->type = OVERRUN;
list_add_tail(&entry2->l, &bad_list);
fprintf(log_op, "%s%s corrupt, buffer overrun [%3d:%3d], %9p, %4zu at %s, %3d, %s\n",
format_time(), !is_realloc ? "free" : "realloc",
entry->seq_num, number_alloc_list, buffer,
entry->size, file,
line, function);
dump_buffer(entry->ptr,
entry->size + sizeof (check), log_op, TIME_STR_LEN);
fprintf(log_op, "%*sCheck_sum\n", TIME_STR_LEN, "");
dump_buffer((char *) &check,
sizeof(check), log_op, TIME_STR_LEN);
__set_bit(MEM_ERR_DETECT_BIT, &debug);
}
mem_allocated -= entry->size;
if (!size) {
free(buffer);
if (is_realloc) {
fprintf(log_op, "%s%-7s[%3d:%3d], %9p, %4zu at %s, %3d, %s -> %9s, %4s at %s, %3d, %s\n",
format_time(), "realloc", entry->seq_num,
number_alloc_list, entry->ptr,
entry->size, entry->file,
entry->line, entry->func,
"made free", "", file, line, function);
/* Record bad realloc */
entry2 = get_free_alloc_entry();
*entry2 = *entry;
entry2->type = REALLOC_ZERO_SIZE;
entry2->file = file;
entry2->line = line;
entry2->func = function;
list_add_tail(&entry2->l, &bad_list);
}
else
fprintf(log_op, "%s%-7s[%3d:%3d], %9p, %4zu at %s, %3d, %s -> %9s, %4s at %s, %3d, %s\n",
format_time(), "free", entry->seq_num,
number_alloc_list, entry->ptr,
entry->size, entry->file,
entry->line, entry->func,
"NULL", "", file, line, function);
#ifdef _MEM_CHECK_LOG_
if (__test_bit(MEM_CHECK_LOG_BIT, &debug))
log_message(LOG_INFO, "%-7s[%3d:%3d], %9p, %4zu at %s, %3d, %s",
is_realloc ? "realloc" : "free",
entry->seq_num, number_alloc_list, buffer,
entry->size, file, line, function);
#endif
entry->file = file;
entry->line = line;
entry->func = function;
entry->type = LAST_FREE;
rb_erase(&entry->t, &alloc_list);
list_add_tail(&entry->l, &free_list);
free_list_size++;
number_alloc_list--;
return NULL;
}
buffer = realloc(buffer, size + sizeof (unsigned long));
mem_allocated += size;
if (mem_allocated > max_mem_allocated)
max_mem_allocated = mem_allocated;
fprintf(log_op, "%srealloc[%3d:%3d], %9p, %4zu at %s, %3d, %s -> %9p, %4zu at %s, %3d, %s\n",
format_time(), entry->seq_num,
number_alloc_list, entry->ptr,
entry->size, entry->file,
entry->line, entry->func,
buffer, size, file, line, function);
#ifdef _MEM_CHECK_LOG_
if (__test_bit(MEM_CHECK_LOG_BIT, &debug))
log_message(LOG_INFO, "realloc[%3d:%3d], %9p, %4zu at %s, %3d, %s -> %9p, %4zu at %s, %3d, %s",
entry->seq_num, number_alloc_list, entry->ptr,
entry->size, entry->file,
entry->line, entry->func,
buffer, size, file, line, function);
#endif
*(unsigned long *) ((char *) buffer + size) = size + CHECK_VAL;
if (entry->ptr != buffer) {
rb_erase(&entry->t, &alloc_list);
entry->ptr = buffer;
rb_insert_sort(&alloc_list, entry, t, memcheck_ptr_cmp);
} else
entry->ptr = buffer;
entry->size = size;
entry->file = file;
entry->line = line;
entry->func = function;
num_reallocs++;
return buffer;
}
void
keepalived_free(void *buffer, const char *file, const char *function, int line)
{
keepalived_free_realloc_common(buffer, 0, file, function, line, false);
}
void *
keepalived_realloc(void *buffer, size_t size, const char *file,
const char *function, int line)
{
return keepalived_free_realloc_common(buffer, size, file, function, line, true);
}
static void
keepalived_alloc_log(bool final)
{
unsigned int overrun = 0, badptr = 0, zero_size = 0;
size_t sum = 0;
MEMCHECK *entry;
if (final) {
/* If this is a forked child, we don't want the dump */
if (skip_mem_check_final)
return;
fprintf(log_op, "\n---[ Keepalived memory dump for (%s) ]---\n\n", terminate_banner);
}
else
fprintf(log_op, "\n---[ Keepalived memory dump for (%s) at %s ]---\n\n", terminate_banner, format_time());
/* List the blocks currently allocated */
if (!RB_EMPTY_ROOT(&alloc_list)) {
fprintf(log_op, "Entries %s\n\n", final ? "not released" : "currently allocated");
rb_for_each_entry(entry, &alloc_list, t) {
sum += entry->size;
fprintf(log_op, "%9p [%3d:%3d], %4zu at %s, %3d, %s",
entry->ptr, entry->seq_num, number_alloc_list,
entry->size, entry->file, entry->line, entry->func);
if (entry->type != ALLOCATED)
fprintf(log_op, " type = %d", entry->type);
fprintf(log_op, "\n");
}
}
if (!list_empty(&bad_list)) {
if (!RB_EMPTY_ROOT(&alloc_list))
fprintf(log_op, "\n");
fprintf(log_op, "Bad entry list\n\n");
list_for_each_entry(entry, &bad_list, l) {
switch (entry->type) {
case FREE_NULL:
badptr++;
fprintf(log_op, "%9s %9s, %4s at %s, %3d, %s - null pointer to free\n",
"NULL", "", "", entry->file, entry->line, entry->func);
break;
case REALLOC_NULL:
badptr++;
fprintf(log_op, "%9s %9s, %4zu at %s, %3d, %s - null pointer to realloc (converted to malloc)\n",
"NULL", "", entry->size, entry->file, entry->line, entry->func);
break;
case FREE_NOT_ALLOC:
badptr++;
fprintf(log_op, "%9p %9s, %4s at %s, %3d, %s - pointer not found for free\n",
entry->ptr, "", "", entry->file, entry->line, entry->func);
break;
case REALLOC_NOT_ALLOC:
badptr++;
fprintf(log_op, "%9p %9s, %4zu at %s, %3d, %s - pointer not found for realloc\n",
entry->ptr, "", entry->size, entry->file, entry->line, entry->func);
break;
case DOUBLE_FREE:
badptr++;
fprintf(log_op, "%9p %9s, %4s at %s, %3d, %s - double free of pointer\n",
entry->ptr, "", "", entry->file, entry->line, entry->func);
break;
case REALLOC_DOUBLE_FREE:
badptr++;
fprintf(log_op, "%9p %9s, %4zu at %s, %3d, %s - realloc 0 size already freed\n",
entry->ptr, "", entry->size, entry->file, entry->line, entry->func);
break;
case OVERRUN:
overrun++;
fprintf(log_op, "%9p [%3d:%3d], %4zu at %s, %3d, %s - buffer overrun\n",
entry->ptr, entry->seq_num, number_alloc_list,
entry->size, entry->file, entry->line, entry->func);
break;
case MALLOC_ZERO_SIZE:
zero_size++;
fprintf(log_op, "%9p [%3d:%3d], %4zu at %s, %3d, %s - malloc zero size\n",
entry->ptr, entry->seq_num, number_alloc_list,
entry->size, entry->file, entry->line, entry->func);
break;
case REALLOC_ZERO_SIZE:
zero_size++;
fprintf(log_op, "%9p [%3d:%3d], %4zu at %s, %3d, %s - realloc zero size (handled as free)\n",
entry->ptr, entry->seq_num, number_alloc_list,
entry->size, entry->file, entry->line, entry->func);
break;
case ALLOCATED: /* not used - avoid compiler warning */
case FREE_SLOT:
case LAST_FREE:
break;
}
}
}
fprintf(log_op, "\n\n---[ Keepalived memory dump summary for (%s) ]---\n", terminate_banner);
fprintf(log_op, "Total number of bytes %s...: %zu\n", final ? "not freed" : "allocated", sum);
fprintf(log_op, "Number of entries %s.......: %d\n", final ? "not freed" : "allocated", number_alloc_list);
fprintf(log_op, "Maximum allocated entries.........: %d\n", max_alloc_list);
fprintf(log_op, "Maximum memory allocated..........: %zu\n", max_mem_allocated);
fprintf(log_op, "Number of mallocs.................: %d\n", num_mallocs);
fprintf(log_op, "Number of reallocs................: %d\n", num_reallocs);
fprintf(log_op, "Number of bad entries.............: %d\n", badptr);
fprintf(log_op, "Number of buffer overrun..........: %d\n", overrun);
fprintf(log_op, "Number of 0 size allocations......: %d\n\n", zero_size);
if (sum != mem_allocated)
fprintf(log_op, "ERROR - sum of allocated %zu != mem_allocated %zu\n", sum, mem_allocated);
if (final) {
if (sum || number_alloc_list || badptr || overrun)
fprintf(log_op, "=> Program seems to have some memory problem !!!\n\n");
else
fprintf(log_op, "=> Program seems to be memory allocation safe...\n\n");
}
}
static void
keepalived_free_final(void)
{
keepalived_alloc_log(true);
}
void
keepalived_alloc_dump(void)
{
keepalived_alloc_log(false);
}
void
mem_log_init(const char* prog_name, const char *banner)
{
size_t log_name_len;
char *log_name;
if (__test_bit(LOG_CONSOLE_BIT, &debug)) {
log_op = stderr;
return;
}
if (log_op)
fclose(log_op);
log_name_len = 5 + strlen(prog_name) + 5 + 7 + 4 + 1; /* "/tmp/" + prog_name + "_mem." + PID + ".log" + '\0" */
log_name = malloc(log_name_len);
if (!log_name) {
log_message(LOG_INFO, "Unable to malloc log file name");
log_op = stderr;
return;
}
snprintf(log_name, log_name_len, "/tmp/%s_mem.%d.log", prog_name, getpid());
log_op = fopen_safe(log_name, "w");
if (log_op == NULL) {
log_message(LOG_INFO, "Unable to open %s for appending", log_name);
log_op = stderr;
}
else {
int fd = fileno(log_op);
/* We don't want any children to inherit the log file */
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
/* Make the log output line buffered. This was to ensure that
* children didn't inherit the buffer, but the CLOEXEC above
* should resolve that. */
setlinebuf(log_op);
fprintf(log_op, "\n");
}
free(log_name);
terminate_banner = banner;
}
void skip_mem_dump(void)
{
skip_mem_check_final = true;
}
void enable_mem_log_termination(void)
{
atexit(keepalived_free_final);
}
#endif