Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions libc/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
"LIBC_CONF_SETJMP_AARCH64_RESTORE_PLATFORM_REGISTER": {
"value": true,
"doc": "Make setjmp save the value of x18, and longjmp restore it. The AArch64 ABI delegates this register to platform ABIs, which can choose whether to make it caller-saved."
},
"LIBC_CONF_SETJMP_FORTIFICATION": {
"value": true,
"doc": "Protect jmp_buf by masking its contents and storing a simple checksum, to make it harder for an attacker to read meaningful information from a jmp_buf or to modify it. This is only supported on x86-64 Linux."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will eventually need to support shadow stacks (part of Intel's CET): https://libc-alpha.sourceware.narkive.com/KcCIyBg9/patch-linux-x86-support-shadow-stack-pointer-in-setjmp-longjmp. It's perhaps worth discussing if we'll want to have 2 or 3 configs for fortification.

For instance, I'd imaging we'd want full fortification or no fortification. But I wonder if shadow stacks make checksumming irrelevant? Hmm...I'll need to think about that more.

}
},
"time": {
Expand Down
1 change: 1 addition & 0 deletions libc/docs/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ to learn about the defaults for your platform and target.
- ``LIBC_CONF_SCANF_DISABLE_INDEX_MODE``: Disable index mode in the scanf format string.
* **"setjmp" options**
- ``LIBC_CONF_SETJMP_AARCH64_RESTORE_PLATFORM_REGISTER``: Make setjmp save the value of x18, and longjmp restore it. The AArch64 ABI delegates this register to platform ABIs, which can choose whether to make it caller-saved.
- ``LIBC_CONF_SETJMP_FORTIFICATION``: Protect jmp_buf by masking its contents and storing a simple checksum, to make it harder for an attacker to read meaningful information from a jmp_buf or to modify it. This is only supported on x86-64 Linux.
* **"string" options**
- ``LIBC_CONF_MEMSET_X86_USE_SOFTWARE_PREFETCHING``: Inserts prefetch for write instructions (PREFETCHW) for memset on x86 to recover performance when hardware prefetcher is disabled.
- ``LIBC_CONF_STRING_UNSAFE_WIDE_READ``: Read more than a byte at a time to perform byte-string operations like strlen.
Expand Down
2 changes: 2 additions & 0 deletions libc/include/llvm-libc-types/jmp_buf.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ typedef struct {
#else
#error "__jmp_buf not available for your target architecture."
#endif
// unused if checksum feature is not enabled.
__UINTPTR_TYPE__ __chksum;
} __jmp_buf;

typedef __jmp_buf jmp_buf[1];
Expand Down
22 changes: 22 additions & 0 deletions libc/src/setjmp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
endif()

if (LIBC_CONF_SETJMP_FORTIFICATION)
if (TARGET libc.src.setjmp.${LIBC_TARGET_OS}.checksum
AND LIBC_TARGET_ARCHITECTURE STREQUAL "x86_64")
add_object_library(
checksum
ALIAS
DEPENDS
.${LIBC_TARGET_OS}.checksum
)
set(fortification_deps libc.src.setjmp.checksum)
set(fortification_defs -DLIBC_COPT_SETJMP_FORTIFICATION=1)
else()
message(STATUS "Jmpbuf fortification is enabled but not supported for target ${LIBC_TARGET_ARCHITECTURE} ${LIBC_TARGET_OS}")
set(fortification_deps)
set(fortification_defs -DLIBC_COPT_SETJMP_FORTIFICATION=0)
endif()
endif()

if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE})
endif()
Expand Down
27 changes: 27 additions & 0 deletions libc/src/setjmp/checksum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===-- Implementation header for jmpbuf checksum ---------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_SETJMP_CHECKSUM_H
#define LLVM_LIBC_SRC_SETJMP_CHECKSUM_H

#include "src/__support/common.h"

namespace LIBC_NAMESPACE_DECL {
namespace jmpbuf {

extern __UINTPTR_TYPE__ value_mask;
extern __UINTPTR_TYPE__ checksum_cookie;

// abitrary prime number
LIBC_INLINE constexpr __UINTPTR_TYPE__ ROTATION = 13;
void initialize();
extern "C" [[gnu::cold, noreturn]] void __libc_jmpbuf_corruption();
} // namespace jmpbuf
} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_SETJMP_CHECKSUM_H
11 changes: 11 additions & 0 deletions libc/src/setjmp/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
add_object_library(
checksum
SRCS
checksum.cpp
HDRS
../checksum.h
DEPENDS
libc.src.__support.common
libc.src.__support.OSUtil.osutil
libc.src.stdlib.abort
)
44 changes: 44 additions & 0 deletions libc/src/setjmp/linux/checksum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===-- Implementation for jmpbuf checksum ----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/setjmp/checksum.h"
#include "src/__support/OSUtil/io.h"
#include "src/stdlib/abort.h"
#include <sys/syscall.h>

namespace LIBC_NAMESPACE_DECL {
namespace jmpbuf {
// random bytes from https://www.random.org/cgi-bin/randbyte?nbytes=8&format=h
// the cookie should not be zero otherwise it will be a bad seed as a multiplier
__UINTPTR_TYPE__ value_mask =
static_cast<__UINTPTR_TYPE__>(0x3899'f0d3'5005'd953ull);
__UINTPTR_TYPE__ checksum_cookie =
static_cast<__UINTPTR_TYPE__>(0xc7d9'd341'6afc'33f2ull);
Comment on lines +18 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
__UINTPTR_TYPE__ value_mask =
static_cast<__UINTPTR_TYPE__>(0x3899'f0d3'5005'd953ull);
__UINTPTR_TYPE__ checksum_cookie =
static_cast<__UINTPTR_TYPE__>(0xc7d9'd341'6afc'33f2ull);
auto value_mask =
static_cast<__UINTPTR_TYPE__>(0x3899'f0d3'5005'd953ull);
auto checksum_cookie =
static_cast<__UINTPTR_TYPE__>(0xc7d9'd341'6afc'33f2ull);


// initialize the checksum state
void initialize() {
union {
struct {
__UINTPTR_TYPE__ entropy0;
__UINTPTR_TYPE__ entropy1;
};
char buffer[sizeof(__UINTPTR_TYPE__) * 2];
};
syscall_impl<long>(SYS_getrandom, buffer, sizeof(buffer), 0);
// add in additional entropy
jmpbuf::value_mask ^= entropy0;
jmpbuf::checksum_cookie ^= entropy1;
}

extern "C" [[gnu::cold, noreturn]] void __libc_jmpbuf_corruption() {
write_to_stderr("invalid checksum detected in longjmp\n");
abort();
}

} // namespace jmpbuf
} // namespace LIBC_NAMESPACE_DECL
4 changes: 4 additions & 0 deletions libc/src/setjmp/x86_64/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ add_entrypoint_object(
../setjmp_impl.h
DEPENDS
libc.hdr.types.jmp_buf
${fortification_deps}
COMPILE_OPTIONS
-O3
${fortification_defs}
)

add_entrypoint_object(
Expand All @@ -18,7 +20,9 @@ add_entrypoint_object(
../longjmp.h
DEPENDS
libc.hdr.types.jmp_buf
${fortification_deps}
COMPILE_OPTIONS
-O3
-fomit-frame-pointer
${fortification_defs}
)
147 changes: 147 additions & 0 deletions libc/src/setjmp/x86_64/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//===-- Common macros for jmpbuf -------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LIBC_SRC_SETJMP_X86_64_COMMON_H
#define LIBC_SRC_SETJMP_X86_64_COMMON_H

#include "include/llvm-libc-macros/offsetof-macro.h"

//===----------------------------------------------------------------------===//
// Architecture specific macros for x86_64.
//===----------------------------------------------------------------------===//

#ifdef __i386__
#define RET_REG eax
#define BASE_REG ecx
#define MUL_REG edx
#define STACK_REG esp
#define PC_REG eip
#define NORMAL_STORE_REGS ebx, esi, edi, ebp
#define STORE_ALL_REGS(M) M(ebx) M(esi) M(edi) M(ebp)
#define LOAD_ALL_REGS(M) M(ebx) M(esi) M(edi) M(ebp) M(esp)
#define DECLARE_ALL_REGS(M) M(ebx), M(esi), M(edi), M(ebp), M(esp), M(eip)
#define LOAD_BASE() "mov 4(%%esp), %%ecx\n\t"
#define CALCULATE_RETURN_VALUE() \
"mov 0x8(%%esp), %%eax" \
"cmp $0x1, %%eax\n\t" \
"adc $0x0, %%eax\n\t"
#else
#define RET_REG rax
#define BASE_REG rdi
#define MUL_REG rdx
#define STACK_REG rsp
#define PC_REG rip
#define STORE_ALL_REGS(M) M(rbx) M(rbp) M(r12) M(r13) M(r14) M(r15)
#define LOAD_ALL_REGS(M) M(rbx) M(rbp) M(r12) M(r13) M(r14) M(r15) M(rsp)
#define DECLARE_ALL_REGS(M) \
M(rbx), M(rbp), M(r12), M(r13), M(r14), M(r15), M(rsp), M(rip)
#define LOAD_BASE()
#define CALCULATE_RETURN_VALUE() \
"cmp $0x1, %%esi\n\t" \
"adc $0x0, %%esi\n\t" \
"mov %%rsi, %%rax\n\t"
#endif

//===----------------------------------------------------------------------===//
// Utility macros.
//===----------------------------------------------------------------------===//

#define _STR(X) #X
#define STR(X) _STR(X)
#define REG(X) "%%" STR(X)
#define XOR(X, Y) "xor " REG(X) ", " REG(Y) "\n\t"
#define MOV(X, Y) "mov " REG(X) ", " REG(Y) "\n\t"
#define STORE(R, OFFSET, BASE) \
"mov " REG(R) ", %c[" STR(OFFSET) "](" REG(BASE) ")\n\t"
#define LOAD(OFFSET, BASE, R) \
"mov %c[" STR(OFFSET) "](" REG(BASE) "), " REG(R) "\n\t"
#define COMPUTE_STACK_TO_RET() \
"lea " STR(__SIZEOF_POINTER__) "(" REG(STACK_REG) "), " REG(RET_REG) "\n\t"
#define COMPUTE_PC_TO_RET() "mov (" REG(STACK_REG) "), " REG(RET_REG) "\n\t"
#define RETURN() "ret\n\t"
#define DECLARE_OFFSET(X) [X] "i"(offsetof(__jmp_buf, X))
#define CMP_MEM_REG(OFFSET, BASE, DST) \
"cmp %c[" STR(OFFSET) "](" REG(BASE) "), " REG(DST) "\n\t"
#define JNE_LABEL(LABEL) "jne " STR(LABEL) "\n\t"

//===----------------------------------------------------------------------===//
// Checksum related macros.
//===----------------------------------------------------------------------===//
// For now, the checksum is computed with a simple multiply-xor-rotation
// algorithm. The pesudo code is as follows:
//
// def checksum(x, acc):
// masked = x ^ MASK
// high, low = full_multiply(masked, acc)
// return rotate(high ^ low, ROTATION)
//
// Similar other multiplication-based hashing, zero inputs
// for the `full_multiply` function may pollute the checksum with zero.
// However, user inputs are always masked where the initial ACC amd MASK are
// generated with random entropy and ROTATION is a fixed prime number. It should
// be of a ultra-low chance for masked or acc being zero given a good quality of
// system-level entropy.
//
// Notice that on x86-64, one-operand form of `mul` instruction:
// mul %rdx
// has the following effect:
// RAX = LOW(RDX * RAX)
// RDX = HIGH(RDX * RAX)
//===----------------------------------------------------------------------===//

#if LIBC_COPT_SETJMP_FORTIFICATION
#define XOR_MASK(X) "xor %[value_mask], " REG(X) "\n\t"
#define MUL(X) "mul " REG(X) "\n\t"
#define ROTATE(X) "rol $%c[rotation], " REG(X) "\n\t"
#define ACCUMULATE_CHECKSUM() MUL(MUL_REG) XOR(RET_REG, MUL_REG) ROTATE(MUL_REG)

#define LOAD_CHKSUM_STATE_REGS() "mov %[checksum_cookie], " REG(MUL_REG) "\n\t"

#define STORE_REG(SRC) \
MOV(SRC, RET_REG) XOR_MASK(RET_REG) STORE(RET_REG, SRC, BASE_REG)
#define STORE_STACK() \
COMPUTE_STACK_TO_RET() \
XOR_MASK(RET_REG) \
STORE(RET_REG, STACK_REG, BASE_REG)

#define STORE_PC() \
COMPUTE_PC_TO_RET() \
XOR_MASK(RET_REG) \
STORE(RET_REG, PC_REG, BASE_REG)

#define STORE_CHECKSUM() STORE(MUL_REG, __chksum, BASE_REG)
#define EXAMINE_CHECKSUM() \
LOAD(PC_REG, BASE_REG, RET_REG) \
ACCUMULATE_CHECKSUM() \
CMP_MEM_REG(__chksum, BASE_REG, MUL_REG) \
JNE_LABEL(__libc_jmpbuf_corruption)

#define RESTORE_PC() \
LOAD(PC_REG, BASE_REG, BASE_REG) \
XOR_MASK(BASE_REG) \
"jmp *" REG(BASE_REG)
#define RESTORE_REG(SRC) \
LOAD(SRC, BASE_REG, RET_REG) \
MOV(RET_REG, SRC) \
ACCUMULATE_CHECKSUM() XOR_MASK(SRC)
#else
#define XOR_MASK(X)
#define ACCUMULATE_CHECKSUM()
#define LOAD_CHKSUM_STATE_REGS()
#define STORE_REG(SRC) STORE(SRC, SRC, BASE_REG)
#define STORE_STACK() COMPUTE_STACK_TO_RET() STORE(RET_REG, STACK_REG, BASE_REG)
#define STORE_PC() COMPUTE_PC_TO_RET() STORE(RET_REG, PC_REG, BASE_REG)
#define STORE_CHECKSUM()
#define EXAMINE_CHECKSUM()
#define RESTORE_PC() "jmp *%c[" STR(PC_REG) "](" REG(BASE_REG) ")\n\t"
#define RESTORE_REG(SRC) LOAD(SRC, BASE_REG, SRC)
#endif

#define STORE_REG_ACCUMULATE(SRC) STORE_REG(SRC) ACCUMULATE_CHECKSUM()

#endif // LIBC_SRC_SETJMP_X86_64_COMMON_H
69 changes: 27 additions & 42 deletions libc/src/setjmp/x86_64/longjmp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,44 @@
//===----------------------------------------------------------------------===//

#include "src/setjmp/longjmp.h"
#include "include/llvm-libc-macros/offsetof-macro.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"

#if !defined(LIBC_TARGET_ARCH_IS_X86)
#error "Invalid file include"
#endif

namespace LIBC_NAMESPACE_DECL {
#include "src/setjmp/x86_64/common.h"

#ifdef __i386__
[[gnu::naked]]
LLVM_LIBC_FUNCTION(void, longjmp, (jmp_buf, int)) {
asm(R"(
mov 0x4(%%esp), %%ecx
mov 0x8(%%esp), %%eax
cmpl $0x1, %%eax
adcl $0x0, %%eax
#if LIBC_COPT_SETJMP_FORTIFICATION
#include "src/setjmp/checksum.h"
#endif

mov %c[ebx](%%ecx), %%ebx
mov %c[esi](%%ecx), %%esi
mov %c[edi](%%ecx), %%edi
mov %c[ebp](%%ecx), %%ebp
mov %c[esp](%%ecx), %%esp
namespace LIBC_NAMESPACE_DECL {

jmp *%c[eip](%%ecx)
)" ::[ebx] "i"(offsetof(__jmp_buf, ebx)),
[esi] "i"(offsetof(__jmp_buf, esi)), [edi] "i"(offsetof(__jmp_buf, edi)),
[ebp] "i"(offsetof(__jmp_buf, ebp)), [esp] "i"(offsetof(__jmp_buf, esp)),
[eip] "i"(offsetof(__jmp_buf, eip)));
}
#else
[[gnu::naked]]
LLVM_LIBC_FUNCTION(void, longjmp, (jmp_buf, int)) {
asm(R"(
cmpl $0x1, %%esi
adcl $0x0, %%esi
movq %%rsi, %%rax
[[gnu::naked]] LLVM_LIBC_FUNCTION(void, longjmp, (jmp_buf, int)) {
asm volatile(
// clang-format off
LOAD_BASE()
LOAD_CHKSUM_STATE_REGS()
LOAD_ALL_REGS(RESTORE_REG)
EXAMINE_CHECKSUM()
CALCULATE_RETURN_VALUE()
RESTORE_PC()
// clang-format on
:
#if LIBC_COPT_SETJMP_FORTIFICATION
[value_mask] "=m"(jmpbuf::value_mask)
#endif
: DECLARE_ALL_REGS(DECLARE_OFFSET)
#if LIBC_COPT_SETJMP_FORTIFICATION
// clang-format off
,[rotation] "i"(jmpbuf::ROTATION)
,[__chksum] "i"(offsetof(__jmp_buf, __chksum))
,[checksum_cookie] "m"(jmpbuf::checksum_cookie)

movq %c[rbx](%%rdi), %%rbx
movq %c[rbp](%%rdi), %%rbp
movq %c[r12](%%rdi), %%r12
movq %c[r13](%%rdi), %%r13
movq %c[r14](%%rdi), %%r14
movq %c[r15](%%rdi), %%r15
movq %c[rsp](%%rdi), %%rsp
jmpq *%c[rip](%%rdi)
)" ::[rbx] "i"(offsetof(__jmp_buf, rbx)),
[rbp] "i"(offsetof(__jmp_buf, rbp)), [r12] "i"(offsetof(__jmp_buf, r12)),
[r13] "i"(offsetof(__jmp_buf, r13)), [r14] "i"(offsetof(__jmp_buf, r14)),
[r15] "i"(offsetof(__jmp_buf, r15)), [rsp] "i"(offsetof(__jmp_buf, rsp)),
[rip] "i"(offsetof(__jmp_buf, rip)));
}
#endif
: STR(RET_REG), STR(BASE_REG), STR(MUL_REG));
}

} // namespace LIBC_NAMESPACE_DECL
Loading
Loading