blob: 5054567baaba99dd3a640c2807684d30baef7abe [file] [log] [blame]
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <linux/memfd.h>
/* bits to get memfd_create to work */
#define MFD_SECRET 0x0008U
#define MFD_SECRET_IOCTL '-'
#define MFD_SECRET_EXCLUSIVE _IOW(MFD_SECRET_IOCTL, 0x13, unsigned long)
#define MFD_SECRET_UNCACHED _IOW(MFD_SECRET_IOCTL, 0x14, unsigned long)
/* glibc should have defined this by now, sigh */
static inline int memfd_create(const char *name, unsigned int flags)
{
return syscall(__NR_memfd_create, name, flags);
}
/* segment size. Matches hugepage size */
#define SEG_SIZE 2*1024*1024
static void *secure_page;
#define CHUNK_SIZE (2 * sizeof(size_t))
#define CHUNK_ALIGNMENT 0xf
#define MIN_FREE_CHUNK 256
static size_t pad_request(size_t s)
{
return (s + CHUNK_SIZE + CHUNK_ALIGNMENT) & ~CHUNK_ALIGNMENT;
}
#define PINUSE_BIT 0x01
#define CINUSE_BIT 0x02
#define FLAG_BITS (CINUSE_BIT | PINUSE_BIT)
struct malloc_chunk {
size_t prev_foot; /* Size of previous chunk (if free). */
size_t head; /* Size and inuse bits. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
};
struct segptr {
void *base;
/* no size because they're always SEG_SIZE */
struct segptr *next;
};
struct malloc_state {
struct segptr seg;
struct malloc_chunk *free;
};
static int in_use(struct malloc_chunk *c)
{
return c->head & CINUSE_BIT ? 1 : 0;
}
static int prev_in_use(struct malloc_chunk *c)
{
return c->head & PINUSE_BIT ? 1 : 0;
}
static void check(int cond, const char *str)
{
if (cond) {
perror(str);
exit(1);
}
}
static void *chunk2mem(struct malloc_chunk *c)
{
return (char *)c + CHUNK_SIZE;
}
static struct malloc_chunk *mem2chunk(void *p)
{
return (struct malloc_chunk *)((char *)p - CHUNK_SIZE);
}
static size_t chunk_size(struct malloc_chunk *c)
{
return c->head & ~FLAG_BITS;
}
static struct malloc_chunk *next_chunk(struct malloc_chunk *c)
{
return (struct malloc_chunk *)((char*)c + chunk_size(c));
}
static struct malloc_chunk *prev_chunk(struct malloc_chunk *c)
{
return (struct malloc_chunk *)((char*)c - c->prev_foot);
}
static struct malloc_state *m;
void __attribute__ ((constructor)) preload_setup(void)
{
int fd = memfd_create("secure", MFD_CLOEXEC|MFD_SECRET);
int ret;
void *p;
struct malloc_chunk *c;
const size_t msize = pad_request(sizeof(*m));
check(fd < 0, "memfd_create");
ret = ioctl(fd, MFD_SECRET_EXCLUSIVE);
check(ret < 0, "ioctl");
ret = ftruncate(fd, SEG_SIZE);
check(ret < 0, "ftruncate");
p = mmap(NULL, SEG_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
check(p == MAP_FAILED, "mmap");
c = p;
m = chunk2mem(c);
memset(m, 0, sizeof(*m));
c->head = msize | CINUSE_BIT | PINUSE_BIT;
m->seg.base = p;
c = next_chunk(c);
c->head = (size_t)(((char *)p + SEG_SIZE) - (char *)c) | PINUSE_BIT;
c->prev_foot = msize;
m->free = c;
c->bk = c->fd = c;
}
static struct malloc_chunk *find_free(size_t size)
{
struct malloc_chunk *c, *found = NULL;
if (m->free == m->free->fd)
return NULL;
for (c = m->free; c != m->free; c = c->fd) {
if (chunk_size(c) < size)
continue;
if (found && chunk_size(found) < chunk_size(c))
continue;
found = c;
}
return found;
}
static void link_free_chunk(struct malloc_chunk *c)
{
struct malloc_chunk *f = m->free;
struct malloc_chunk *b = f->bk;
c->fd = f;
f->bk = c;
b->fd = c;
c->bk = b;
}
static void unlink_free_chunk(struct malloc_chunk *c)
{
struct malloc_chunk *f = c->fd;
struct malloc_chunk *b = c->bk;
b->fd = f;
f->bk = b;
}
static void split_free_chunk(struct malloc_chunk *c, size_t size)
{
struct malloc_chunk *new_c;
size_t csize = chunk_size(c);
if (csize < size + MIN_FREE_CHUNK) {
unlink_free_chunk(c);
return;
}
/* here we need to split the chunk, so pad up the size to the min */
if (size < MIN_FREE_CHUNK)
size = MIN_FREE_CHUNK;
/* set the old chunk to the size */
c->head = size | CINUSE_BIT | PINUSE_BIT;
/* get the new part of the split */
new_c = next_chunk(c);
new_c->head = csize - size;
new_c->prev_foot = size;
/* now replace the new chunk with the old chunk */
new_c->fd = c->fd;
new_c->bk = c->bk;
c->bk->fd = new_c;
c->fd->bk = new_c;
}
static void *alloc(size_t size)
{
struct malloc_chunk *c;
size = pad_request(size);
c = find_free(size);
if (c == NULL)
/* FIXME ADD MORE */
return NULL;
split_free_chunk(c, size);
return chunk2mem(c);
}
void *CRYPTO_malloc(size_t size, const char *file, int line)
{
printf("in crypto malloc from %s:%d\n", file, line);
if (size < SEG_SIZE)
return alloc(size);
else
return NULL;
}
void *CRYPTO_free(void *ptr, const char *file, int line)
{
struct malloc_chunk *c, *n;
printf("in crypto frss from %s:%d\n", file, line);
c = mem2chunk(ptr);
/* shred the data */
memset(ptr, 0, chunk_size(c) - CHUNK_SIZE);
n = next_chunk(c);
/* now check for consolidation with previous */
if (!prev_in_use(c)) {
struct malloc_chunk *p = prev_chunk(c);
p->head += chunk_size(c);
/* the new consolidated chunk becomes our current
* chunk for the next free check below. The previous
* chunk was already linked */
c = p;
} else {
link_free_chunk(c);
}
/* and finally consolidation with next */
if (!in_use(n)) {
unlink_free_chunk(n);
c->head += chunk_size(n);
}
}