Safe-Linking is a security mechanism that protects single-linked
lists (such as the fastbins) from being tampered by attackers. The
mechanism makes use of randomness from ASLR (mmap_base), and when
combined with chunk alignment integrity checks, it protects the
pointers from being hijacked by an attacker.
While Safe-Unlinking protects double-linked lists (such as the small
bins), there wasn't any similar protection for attacks against
single-linked lists. This solution protects against 3 common attacks:
* Partial pointer override: modifies the lower bytes (Little Endian)
* Full pointer override: hijacks the pointer to an attacker's location
* Unaligned chunks: pointing the list to an unaligned address
The design assumes an attacker doesn't know where the heap is located,
and uses the ASLR randomness to "sign" the single-linked pointers. We
mark the pointer as P and the location in which it is stored as L, and
the calculation will be:
* PROTECT(P) := (L >> PAGE_SHIFT) XOR (P)
* *L = PROTECT(P)
This way, the random bits from the address L (which start at the bits
in the PAGE_SHIFT position), will be merged with the LSB of the stored
protected pointer. This protection layer prevents an attacker from
modifying the pointer into a controlled value.
An additional check that the chunks are MALLOC_ALIGNed adds an
important layer:
* Attackers can't point to illegal (unaligned) memory addresses
* Attackers must guess correctly the alignment bits
On standard 32 bit Linux machines, an attacker will directly fail 7
out of 8 times, and on 64 bit machines it will fail 15 out of 16
times.
The proposed solution adds 3-4 asm instructions per malloc()/free()
and therefore has only minor performance implications if it has
any. A similar protection was added to Chromium's version of TCMalloc
in 2013, and according to their documentation the performance overhead
was less than 2%.
For more information, feel free to check out our White Paper which can be
found here:
https://github.com/gperftools/gperftools/files/4023520/Safe-Linking-White-P…
---
libc/stdlib/malloc-standard/free.c | 5 +++--
libc/stdlib/malloc-standard/mallinfo.c | 3 ++-
libc/stdlib/malloc-standard/malloc.c | 6 ++++--
libc/stdlib/malloc-standard/malloc.h | 12 ++++++++++++
4 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/libc/stdlib/malloc-standard/free.c
b/libc/stdlib/malloc-standard/free.c
index a2d765d41..f3602cf48 100644
--- a/libc/stdlib/malloc-standard/free.c
+++ b/libc/stdlib/malloc-standard/free.c
@@ -214,8 +214,9 @@ void attribute_hidden __malloc_consolidate(mstate av)
*fb = 0;
do {
+ CHECK_PTR(p);
check_inuse_chunk(p);
- nextp = p->fd;
+ nextp = REVEAL_PTR(&p->fd, p->fd);
/* Slightly streamlined version of consolidation code in free() */
size = p->size & ~PREV_INUSE;
@@ -308,7 +309,7 @@ void free(void* mem)
set_fastchunks(av);
fb = &(av->fastbins[fastbin_index(size)]);
- p->fd = *fb;
+ p->fd = PROTECT_PTR(&p->fd, *fb);
*fb = p;
}
diff --git a/libc/stdlib/malloc-standard/mallinfo.c
b/libc/stdlib/malloc-standard/mallinfo.c
index dbe4d49b8..992322341 100644
--- a/libc/stdlib/malloc-standard/mallinfo.c
+++ b/libc/stdlib/malloc-standard/mallinfo.c
@@ -49,7 +49,8 @@ struct mallinfo mallinfo(void)
fastavail = 0;
for (i = 0; i < NFASTBINS; ++i) {
- for (p = av->fastbins[i]; p != 0; p = p->fd) {
+ for (p = av->fastbins[i]; p != 0; p = REVEAL_PTR(&p->fd, p->fd)) {
+ CHECK_PTR(p);
++nfastblocks;
fastavail += chunksize(p);
}
diff --git a/libc/stdlib/malloc-standard/malloc.c
b/libc/stdlib/malloc-standard/malloc.c
index 1a6d4dc1c..1f898eb29 100644
--- a/libc/stdlib/malloc-standard/malloc.c
+++ b/libc/stdlib/malloc-standard/malloc.c
@@ -260,12 +260,13 @@ void __do_check_malloc_state(void)
assert(p == 0);
while (p != 0) {
+ CHECK_PTR(p);
/* each chunk claims to be inuse */
__do_check_inuse_chunk(p);
total += chunksize(p);
/* chunk belongs in this bin */
assert(fastbin_index(chunksize(p)) == i);
- p = p->fd;
+ p = REVEAL_PTR(&p->fd, p->fd);
}
}
@@ -855,7 +856,8 @@ void* malloc(size_t bytes)
if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
fb = &(av->fastbins[(fastbin_index(nb))]);
if ( (victim = *fb) != 0) {
- *fb = victim->fd;
+ CHECK_PTR(victim);
+ *fb = REVEAL_PTR(&victim->fd, victim->fd);
check_remalloced_chunk(victim, nb);
retval = chunk2mem(victim);
goto DONE;
diff --git a/libc/stdlib/malloc-standard/malloc.h
b/libc/stdlib/malloc-standard/malloc.h
index 44120d388..30a696e5a 100644
--- a/libc/stdlib/malloc-standard/malloc.h
+++ b/libc/stdlib/malloc-standard/malloc.h
@@ -839,6 +839,18 @@ typedef struct malloc_chunk* mfastbinptr;
#define get_max_fast(M) \
((M)->max_fast & ~(FASTCHUNKS_BIT | ANYCHUNKS_BIT))
+/*
+ Safe-Linking:
+ Use randomness from ASLR (mmap_base) to protect single-linked lists
+ of fastbins. Together with allocation alignment checks, this mechanism
+ reduces the risk of pointer hijacking, as was done with Safe-Unlinking
+ in the double-linked lists of smallbins.
+*/
+#define PROTECT_PTR(pos, ptr) ((mchunkptr)((((size_t)pos) >>
PAGE_SHIFT) ^ ((size_t)ptr)))
+#define REVEAL_PTR(pos, ptr) PROTECT_PTR(pos, ptr)
+#define CHECK_PTR(P) \
+ if (!aligned_OK(P)) \
+ abort();
/*
morecore_properties is a status word holding dynamically discovered
--
2.17.1
Hi John,
Ata, John (US) wrote,
> Hi all,
>
>
>
> Has anyone successfully created a toolchain with the latest uClibc and gcc
> 8.3.1?
What issues do you have?
gcc 8.3.0 is the latest release and I see no problems with it.
best regards
Waldemar
On 22/02/20 01:21, Ata, John (US) wrote:
>
> Hi all,
>
>
>
> Has anyone successfully created a toolchain with the latest uClibc and
> gcc 8.3.1?
>
>
>
> Thanks,
>
> ----
>
Closest I have is:
Using built-in specs.
COLLECT_GCC=armv6zk-unknown-linux-uclibceabihf-gcc-8.3.0
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/armv6zk-unknown-linux-uclibceabihf/8.3.0/lto-wrapper
Target: armv6zk-unknown-linux-uclibceabihf
Configured with:
/var/tmp/portage/cross-armv6zk-unknown-linux-uclibceabihf/gcc-8.3.0-r1/work/gcc-8.3.0/configure
--host=x86_64-pc-linux-gnu --target=armv6zk-unknown-linux-uclibceabihf
--build=x86_64-pc-linux-gnu --prefix=/usr
--bindir=/usr/x86_64-pc-linux-gnu/armv6zk-unknown-linux-uclibceabihf/gcc-bin/8.3.0
--includedir=/usr/lib/gcc/armv6zk-unknown-linux-uclibceabihf/8.3.0/include
--datadir=/usr/share/gcc-data/armv6zk-unknown-linux-uclibceabihf/8.3.0
--mandir=/usr/share/gcc-data/armv6zk-unknown-linux-uclibceabihf/8.3.0/man
--infodir=/usr/share/gcc-data/armv6zk-unknown-linux-uclibceabihf/8.3.0/info
--with-gxx-include-dir=/usr/lib/gcc/armv6zk-unknown-linux-uclibceabihf/8.3.0/include/g++-v8
--with-python-dir=/share/gcc-data/armv6zk-unknown-linux-uclibceabihf/8.3.0/python
--enable-languages=c,c++,fortran --enable-obsolete --enable-secureplt
--disable-werror --with-system-zlib --enable-nls --without-included-gettext
--enable-checking=release --with-bugurl=https://bugs.gentoo.org/
--with-pkgversion='Gentoo 8.3.0-r1 p1.1' --disable-esp
--enable-libstdcxx-time --enable-poison-system-directories
--with-sysroot=/usr/armv6zk-unknown-linux-uclibceabihf --disable-bootstrap
--disable-__cxa_atexit --enable-tls --disable-multilib --disable-altivec
--disable-fixed-point --with-float=hard --with-arch=armv6zk
--with-float=hard --with-fpu=vfp --enable-libgomp --disable-libmudflap
--disable-libssp --disable-libmpx --disable-systemtap
--disable-vtable-verify --disable-libvtv --enable-lto --without-isl
--disable-libsanitizer --enable-default-pie --enable-default-ssp
Thread model: posix
gcc version 8.3.0 (Gentoo 8.3.0-r1 p1.1)
uclibc-ng version is 1.0.31.
But I am aware it's not generating good ARM executables because of another bug.
http://distfiles.gentoo.org/releases/amd64/autobuilds/current-stage3-amd64-…
appears to have a functional x86_64 toolchain, unless I'm mistaken, but
this is quite likely to have rolled over to gcc-9.2 now. We certainly would
have had 8.3.0 if this build series has been continuous over the past year
or so.
Is it specifically 8.3._1_ you're having a problem with?
Hello,
We have noticed random deadlocks in malloc/free when using uClibc-ng +
Thumb1 + NPTL compiled for ARMv6 in multi-threaded environment.
Steps to reproduce
1. Build Buildroot 2019.02.9 SDK with the following configuration:
BR2_arm=y
BR2_arm1176jz_s=y
BR2_ARM_INSTRUCTIONS_THUMB=y
BR2_SHARED_STATIC_LIBS=y
BR2_TOOLCHAIN_BUILDROOT_WCHAR=y
BR2_TOOLCHAIN_BUILDROOT_USE_SSP=y
BR2_TOOLCHAIN_BUILDROOT_CXX=y
2. Cross compile the sample (see below) application with the SDK using
static linking:
$ arm-buildroot-linux-uclibcgnueabi-gcc -static -g deadlock.c \
-lpthread -o deadlock
3. Run the application with at least two threads
3.1. Run the application using QEMU static emulation:
$ qemu-arm-static ./deadlock 4 1000000
3.2. Run the application on a device with ARMv6 support (ex. any RPi
with Raspbian image):
$ ./deadlock 4 1000000
Expected results:
The application should always exit cleanly.
Actual results:
The application often deadlocks in both cases.
Notes:
1. The bug is also reproducible with latest uClibc-ng (v1.0.32)
2. According to our tests the endianness does not matter
3. The callstack when deadlock appears:
(gdb) info threads
Id Target Id Frame
* 1 LWP 11653 "mutex" 0x000165b2 in pthread_join ()
2 LWP 11656 "mutex" 0x00014cac in __lll_lock_wait ()
3 LWP 11657 "mutex" 0x00014ce2 in __lll_lock_wait ()
4 LWP 11658 "mutex" 0x00014cac in __lll_lock_wait ()
5 LWP 11659 "mutex" 0x00014cac in __lll_lock_wait ()
(gdb) t 1
[Switching to thread 1 (LWP 11653)]
#0 0x000165b2 in pthread_join ()
(gdb) bt
#0 0x000165b2 in pthread_join ()
#1 0x00010396 in main (argc=3, argv=0x7efff784) at deadlock.c:42
(gdb) t 2
[Switching to thread 2 (LWP 11656)]
#0 0x00014cac in __lll_lock_wait ()
(gdb) bt
#0 0x00014cac in __lll_lock_wait ()
#1 0x00016b7e in pthread_mutex_lock ()
#2 0x0001199e in __libc_malloc ()
#3 0x00010278 in do_request (data=0x1) at deadlock.c:13
#4 0x00015b36 in start_thread ()
#5 0x000173f4 in clone ()
(gdb) t 3
[Switching to thread 3 (LWP 11657)]
#0 0x00014ce2 in __lll_lock_wait ()
(gdb) bt
#0 0x00014ce2 in __lll_lock_wait ()
#1 0x00016b7e in pthread_mutex_lock ()
#2 0x000117c2 in __libc_free ()
#3 0x00010284 in do_request (data=0x2) at deadlock.c:15
#4 0x00015b36 in start_thread ()
#5 0x000173f4 in clone ()
(gdb) t 4
[Switching to thread 4 (LWP 11658)]
#0 0x00014cac in __lll_lock_wait ()
(gdb) bt
#0 0x00014cac in __lll_lock_wait ()
#1 0x00016b7e in pthread_mutex_lock ()
#2 0x0001199e in __libc_malloc ()
#3 0x00010278 in do_request (data=0x3) at deadlock.c:13
#4 0x00015b36 in start_thread ()
#5 0x000173f4 in clone ()
(gdb) t 5
[Switching to thread 5 (LWP 11659)]
#0 0x00014cac in __lll_lock_wait ()
(gdb) bt
#0 0x00014cac in __lll_lock_wait ()
#1 0x00016b7e in pthread_mutex_lock ()
#2 0x0001199e in __libc_malloc ()
#3 0x00010278 in do_request (data=0x4) at deadlock.c:13
#4 0x00015b36 in start_thread ()
#5 0x000173f4 in clone ()
Sample application:
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
int count;
void *do_request(void *data)
{
for (int i = 0; i < count; i++) {
if (i % 1000 == 0)
printf("Alloc for %p count %d\n", data, i);
char *buffer = malloc(32 * sizeof(char));
free(buffer);
}
return NULL;
}
int main(int argc, char *argv[])
{
int i, rc;
int threads_count = atoi(argv[1]);
count = atoi(argv[2]);
pthread_t threads[threads_count];
printf("Creating %d threads\n", threads_count);
for (i = 0; i < threads_count; i++) {
rc = pthread_create(&threads[i], NULL, do_request, (int*)(i+1));
if (rc) {
printf("Error creating thread\n");
exit(1);
}
}
for (i = 0; i < threads_count; i++)
if (pthread_join(threads[i], NULL)) {
printf("Error joining thread\n");
exit(1);
}
printf("Done. \n");
return 0;
}
Best regards,
Gyula Farkas
It fixes tst-signal6 and friends.
Signed-off-by: Vladimir Murzin <vladimir.murzin(a)arm.com>
---
libc/sysdeps/linux/aarch64/bits/sigstack.h | 56 ++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 libc/sysdeps/linux/aarch64/bits/sigstack.h
diff --git a/libc/sysdeps/linux/aarch64/bits/sigstack.h b/libc/sysdeps/linux/aarch64/bits/sigstack.h
new file mode 100644
index 0000000..7cb8926
--- /dev/null
+++ b/libc/sysdeps/linux/aarch64/bits/sigstack.h
@@ -0,0 +1,56 @@
+/* sigstack, sigaltstack definitions.
+ Copyright (C) 1998, 1999 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _SIGNAL_H
+# error "Never include this file directly. Use <signal.h> instead"
+#endif
+
+
+#if defined __UCLIBC_SUSV4_LEGACY__ || !defined __UCLIBC_STRICT_HEADERS__
+/* Structure describing a signal stack (obsolete). */
+struct sigstack
+ {
+ void *ss_sp; /* Signal stack pointer. */
+ int ss_onstack; /* Nonzero if executing on this stack. */
+ };
+#endif
+
+
+/* Possible values for `ss_flags.'. */
+enum
+{
+ SS_ONSTACK = 1,
+#define SS_ONSTACK SS_ONSTACK
+ SS_DISABLE
+#define SS_DISABLE SS_DISABLE
+};
+
+/* Minimum stack size for a signal handler. */
+#define MINSIGSTKSZ 5120
+
+/* System default stack size. */
+#define SIGSTKSZ 16384
+
+
+/* Alternate, preferred interface. */
+typedef struct sigaltstack
+ {
+ void *ss_sp;
+ int ss_flags;
+ size_t ss_size;
+ } stack_t;
--
2.7.4