From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-3.1 required=5.0 tests=DKIM_INVALID,DKIM_SIGNED, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED,RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL autolearn=ham autolearn_force=no version=3.4.4 Received: (qmail 12315 invoked from network); 2 Dec 2020 00:56:04 -0000 Received: from mother.openwall.net (195.42.179.200) by inbox.vuxu.org with ESMTPUTF8; 2 Dec 2020 00:56:04 -0000 Received: (qmail 26286 invoked by uid 550); 2 Dec 2020 00:56:00 -0000 Mailing-List: contact musl-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Reply-To: musl@lists.openwall.com Received: (qmail 26256 invoked from network); 2 Dec 2020 00:56:00 -0000 X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dereferenced.org; s=default; t=1606870589; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=jqIYZYVR4qNmFOoLNxJKGHtmVPGHJHCbYjIk22TB4lE=; b=dcicqm0LiKUp19HUSDqKMZNhaVWqWUMZJymyFlxWLw8DFiRQaJn8kM4jIcqa+8TR3OGITI zSoadjRgWheZeuF1JD8pYPZI8FdxhCM6dwLAQo+sEfYLfoQbOI671K/Evz7zWPIlHvHwG3 q9hH/O4Hfnm81p83h+v++D6PVQoESII= From: Ariadne Conill To: musl@lists.openwall.com Cc: Ariadne Conill Date: Tue, 1 Dec 2020 17:55:39 -0700 Message-Id: <20201202005539.6418-1-ariadne@dereferenced.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Migadu-Flow: FLOW_OUT X-Migadu-Auth-User: ariadne@dereferenced.org Subject: [musl] [PATCH] harden against unauthorized writes to the atexit function lists previously, the first atexit list block was stored in BSS, which means an attacker could potentially ascertain its location and modify it, allowing for its abuse as a code execution mechanism. by moving the atexit list into a series of anonymous mmaped pages, we can use mprotect to protect the atexit lists by keeping them readonly when they are not being mutated by the __cxa_atexit() function. --- src/exit/atexit.c | 69 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/src/exit/atexit.c b/src/exit/atexit.c index 854e9fdd..8bf1cfcb 100644 --- a/src/exit/atexit.c +++ b/src/exit/atexit.c @@ -1,5 +1,6 @@ #include #include +#include #include "libc.h" #include "lock.h" #include "fork_impl.h" @@ -9,17 +10,17 @@ #define realloc undef #define free undef -/* Ensure that at least 32 atexit handlers can be registered without malloc */ -#define COUNT 32 - static struct fl { struct fl *next; - void (*f[COUNT])(void *); - void *a[COUNT]; -} builtin, *head; + struct fl_pair { + void (*f)(void *); + void *a; + } pairs[]; +} *head; static int slot; +static size_t slot_max; static volatile int lock[1]; volatile int *const __atexit_lockptr = lock; @@ -27,12 +28,19 @@ void __funcs_on_exit() { void (*func)(void *), *arg; LOCK(lock); - for (; head; head=head->next, slot=COUNT) while(slot-->0) { - func = head->f[slot]; - arg = head->a[slot]; - UNLOCK(lock); - func(arg); - LOCK(lock); + for (; head; head=head->next, slot=slot_max) { + struct fl *next = head->next; + + while(slot-->0) { + func = head->pairs[slot].f; + arg = head->pairs[slot].a; + UNLOCK(lock); + func(arg); + LOCK(lock); + } + + munmap(head, PAGE_SIZE); + head = next; } } @@ -42,28 +50,45 @@ void __cxa_finalize(void *dso) int __cxa_atexit(void (*func)(void *), void *arg, void *dso) { + struct fl *cursor; + LOCK(lock); - /* Defer initialization of head so it can be in BSS */ - if (!head) head = &builtin; + /* Figure out how many slots we can have per page: page size minus one pointer then divided by two for function-argument pairs. */ + slot_max = (PAGE_SIZE - sizeof(void *)) / sizeof(struct fl_pair); + + /* Determine if a new allocation is necessary. */ + if (!head || slot == slot_max) + cursor = 0; + else + cursor = head; - /* If the current function list is full, add a new one */ - if (slot==COUNT) { - struct fl *new_fl = calloc(sizeof(struct fl), 1); - if (!new_fl) { + /* If a new allocation is necessary, allocate it read-write, otherwise make the current atexit list read-write. */ + if (!cursor) { + cursor = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + if (cursor == MAP_FAILED) { UNLOCK(lock); return -1; } - new_fl->next = head; - head = new_fl; + cursor->next = head; + head = cursor; slot = 0; + } else if (mprotect(cursor, PAGE_SIZE, PROT_READ | PROT_WRITE)) { + UNLOCK(lock); + return -1; } /* Append function to the list. */ - head->f[slot] = func; - head->a[slot] = arg; + cursor->pairs[slot].f = func; + cursor->pairs[slot].a = arg; slot++; + /* Mark the atexit list read-only to avoid its abuse. */ + if (mprotect(cursor, PAGE_SIZE, PROT_READ)) { + UNLOCK(lock); + return -1; + } + UNLOCK(lock); return 0; } -- 2.29.2