Implement setjmp cookies on ARM.
Reuse the top bits of _JB_SIGFLAG field previously used to store a
boolean to store a cookie that's validated by [sig]longjmp to make it
harder to use as a ROP gadget. Additionally, encrypt saved registers
with the cookie so that an attacker can't modify a register's value to
a specific value without knowing the cookie.
Bug: http://b/23942752
Change-Id: Id0eb8d06916e89d5d776bfcaa9458f8826717ba3
diff --git a/libc/Android.mk b/libc/Android.mk
index a8c66fa..175fbb4 100644
--- a/libc/Android.mk
+++ b/libc/Android.mk
@@ -250,10 +250,11 @@
# dereferences.
libc_bionic_src_files += bionic/getauxval.cpp
-# These three require getauxval, which isn't available on older platforms.
+# These four require getauxval, which isn't available on older platforms.
libc_bionic_src_files += bionic/getentropy_linux.c
libc_bionic_src_files += bionic/sysconf.cpp
libc_bionic_src_files += bionic/vdso.cpp
+libc_bionic_src_files += bionic/setjmp_cookie.cpp
libc_cxa_src_files := \
bionic/__cxa_guard.cpp \
@@ -347,7 +348,7 @@
$(libc_upstream_openbsd_gdtoa_src_files) \
upstream-openbsd/lib/libc/gdtoa/strtorQ.c \
-# These two depend on getentropy_linux.cpp, which isn't in libc_ndk.a.
+# These two depend on getentropy_linux.c, which isn't in libc_ndk.a.
libc_upstream_openbsd_src_files := \
upstream-openbsd/lib/libc/crypt/arc4random.c \
upstream-openbsd/lib/libc/crypt/arc4random_uniform.c \
diff --git a/libc/arch-arm/bionic/setjmp.S b/libc/arch-arm/bionic/setjmp.S
index 8220c08..a119529 100644
--- a/libc/arch-arm/bionic/setjmp.S
+++ b/libc/arch-arm/bionic/setjmp.S
@@ -51,13 +51,13 @@
// The internal structure of a jmp_buf is totally private.
// Current layout (may change in the future):
//
-// word name description
-// 0 magic magic number
-// 1 sigmask signal mask (not used with _setjmp / _longjmp)
-// 2 float_base base of float registers (d8 to d15)
-// 18 float_state floating-point status and control register
-// 19 core_base base of core registers (r4 to r14)
-// 30 reserved reserved entries (room to grow)
+// word name description
+// 0 sigflag/cookie setjmp cookie in top 31 bits, signal mask flag in low bit
+// 1 sigmask signal mask (not used with _setjmp / _longjmp)
+// 2 float_base base of float registers (d8 to d15)
+// 18 float_state floating-point status and control register
+// 19 core_base base of core registers (r4 to r14)
+// 30 reserved reserved entries (room to grow)
// 64
//
// NOTE: float_base must be at an even word index, since the
@@ -80,33 +80,79 @@
b sigsetjmp
END(_setjmp)
+#define MANGLE_REGISTERS 1
+.macro m_mangle_registers reg
+#if MANGLE_REGISTERS
+ eor r4, r4, \reg
+ eor r5, r5, \reg
+ eor r6, r6, \reg
+ eor r7, r7, \reg
+ eor r8, r8, \reg
+ eor r9, r9, \reg
+ eor r10, r10, \reg
+ eor r11, r11, \reg
+ eor r12, r12, \reg
+ eor r13, r13, \reg
+ eor r14, r14, \reg
+#endif
+.endm
+
+.macro m_unmangle_registers reg
+ m_mangle_registers \reg
+.endm
+
// int sigsetjmp(sigjmp_buf env, int save_signal_mask);
ENTRY(sigsetjmp)
- // Record whether or not we're saving the signal mask.
+ stmfd sp!, {r0, lr}
+ .cfi_def_cfa_offset 8
+ .cfi_rel_offset r0, 0
+ .cfi_rel_offset lr, 4
+
+ mov r0, r1
+ bl __bionic_setjmp_cookie_get
+ mov r1, r0
+
+ ldmfd sp, {r0}
+
+ // Save the setjmp cookie for later.
+ bic r2, r1, #1
+ stmfd sp!, {r2}
+ .cfi_adjust_cfa_offset 4
+
+ // Record the setjmp cookie and whether or not we're saving the signal mask.
str r1, [r0, #(_JB_SIGFLAG * 4)]
// Do we need to save the signal mask?
- teq r1, #0
+ tst r1, #1
beq 1f
- // Get current signal mask.
- stmfd sp!, {r0, r14}
- .cfi_def_cfa_offset 8
- .cfi_rel_offset r0, 0
- .cfi_rel_offset r14, 4
- mov r0, #0
- bl sigblock
- mov r1, r0
- ldmfd sp!, {r0, r14}
- .cfi_def_cfa_offset 0
+ // Align the stack.
+ sub sp, #4
+ .cfi_adjust_cfa_offset 4
- // Save the signal mask.
- str r1, [r0, #(_JB_SIGMASK * 4)]
+ // Save the current signal mask.
+ add r2, r0, #(_JB_SIGMASK * 4)
+ mov r0, #2 // SIG_SETMASK
+ mov r1, #0
+ bl sigprocmask
+
+ // Unalign the stack.
+ add sp, #4
+ .cfi_adjust_cfa_offset -4
1:
+ ldmfd sp!, {r2}
+ .cfi_adjust_cfa_offset -4
+ ldmfd sp!, {r0, lr}
+ .cfi_adjust_cfa_offset -8
+ .cfi_restore r0
+ .cfi_restore lr
+
// Save core registers.
add r1, r0, #(_JB_CORE_BASE * 4)
+ m_mangle_registers r2
stmia r1, {r4-r14}
+ m_unmangle_registers r2
// Save floating-point registers.
add r1, r0, #(_JB_FLOAT_BASE * 4)
@@ -122,29 +168,30 @@
// void siglongjmp(sigjmp_buf env, int value);
ENTRY(siglongjmp)
- // Do we need to restore the signal mask?
- ldr r2, [r0, #(_JB_SIGFLAG * 4)]
- teq r2, #0
- beq 1f
-
- // Restore the signal mask.
- stmfd sp!, {r0, r1, r14}
+ stmfd sp!, {r0, r1, lr}
.cfi_def_cfa_offset 12
.cfi_rel_offset r0, 0
.cfi_rel_offset r1, 4
- .cfi_rel_offset r14, 8
- sub sp, sp, #4 // Align the stack.
- .cfi_adjust_cfa_offset 4
+ .cfi_rel_offset lr, 8
+ // Fetch the signal flag.
+ ldr r1, [r0, #(_JB_SIGFLAG * 4)]
+
+ // Do we need to restore the signal mask?
+ ands r1, r1, #1
+ beq 1f
+
+ // Restore the signal mask.
ldr r0, [r0, #(_JB_SIGMASK * 4)]
bl sigsetmask
- add sp, sp, #4 // Unalign the stack.
- .cfi_adjust_cfa_offset -4
- ldmfd sp!, {r0, r1, r14}
- .cfi_def_cfa_offset 0
-
1:
+ ldmfd sp!, {r0, r1, lr}
+ .cfi_adjust_cfa_offset -12
+ .cfi_restore r0
+ .cfi_restore r1
+ .cfi_restore lr
+
// Restore floating-point registers.
add r2, r0, #(_JB_FLOAT_BASE * 4)
vldmia r2, {d8-d15}
@@ -154,16 +201,24 @@
fmxr fpscr, r2
// Restore core registers.
+ ldr r3, [r0, #(_JB_SIGFLAG * 4)]
+ bic r3, r3, #1
add r2, r0, #(_JB_CORE_BASE * 4)
ldmia r2, {r4-r14}
+ m_unmangle_registers r3
- // Validate sp and r14.
- teq sp, #0
- teqne r14, #0
- bleq longjmperror
+ // Save the return value/address and check the setjmp cookie.
+ stmfd sp!, {r1, lr}
+ .cfi_adjust_cfa_offset 8
+ .cfi_rel_offset lr, 4
+ mov r0, r3
+ bl __bionic_setjmp_cookie_check
- // Set return value.
- mov r0, r1
+ // Restore return value/address.
+ ldmfd sp!, {r0, lr}
+ .cfi_adjust_cfa_offset -8
+ .cfi_restore lr
+
teq r0, #0
moveq r0, #1
bx lr
diff --git a/libc/bionic/libc_init_common.cpp b/libc/bionic/libc_init_common.cpp
index 3ca6c0d..f59fa67 100644
--- a/libc/bionic/libc_init_common.cpp
+++ b/libc/bionic/libc_init_common.cpp
@@ -49,6 +49,7 @@
#include "pthread_internal.h"
extern "C" abort_msg_t** __abort_message_ptr;
+extern "C" void __bionic_setjmp_cookie_init(void);
extern "C" int __system_properties_init(void);
extern "C" int __set_tls(void* ptr);
extern "C" int __set_tid_address(int* tid_address);
@@ -121,6 +122,7 @@
__system_properties_init(); // Requires 'environ'.
+ __bionic_setjmp_cookie_init();
__libc_init_vdso();
}
diff --git a/libc/bionic/setjmp_cookie.cpp b/libc/bionic/setjmp_cookie.cpp
new file mode 100644
index 0000000..cf79e83
--- /dev/null
+++ b/libc/bionic/setjmp_cookie.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <sys/cdefs.h>
+
+#include "private/libc_logging.h"
+
+extern "C" __LIBC_HIDDEN__ int getentropy(void*, size_t);
+static long __bionic_setjmp_cookie;
+
+extern "C" void __bionic_setjmp_cookie_init() {
+ char* random_data = reinterpret_cast<char*>(getauxval(AT_RANDOM));
+ long value = *reinterpret_cast<long*>(random_data + 8);
+
+ // Mask off the last bit to store the signal flag.
+ __bionic_setjmp_cookie = value & ~1;
+}
+
+extern "C" long __bionic_setjmp_cookie_get(long sigflag) {
+ if (sigflag & ~1) {
+ __libc_fatal("unexpected sigflag value: %ld", sigflag);
+ }
+
+ return __bionic_setjmp_cookie | sigflag;
+}
+
+// Aborts if cookie doesn't match, returns the signal flag otherwise.
+extern "C" long __bionic_setjmp_cookie_check(long cookie) {
+ if (__bionic_setjmp_cookie != (cookie & ~1)) {
+ __libc_fatal("setjmp cookie mismatch");
+ }
+
+ return cookie & 1;
+}