Skip to content

[libc] fortify jmp buffer for x86-64 #112769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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": false,
"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
8 changes: 8 additions & 0 deletions libc/config/linux/x86_64/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"setjmp": {
"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.

Perhaps this should be one config. Otherwise, if the user flips one but not the other, then setjmp/longjmp will fail at runtime, right?

}
}
}
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
21 changes: 21 additions & 0 deletions libc/src/setjmp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
if (LIBC_CONF_SETJMP_FORTIFICATION)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
endif()
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(WARNING "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
34 changes: 34 additions & 0 deletions libc/src/setjmp/checksum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//===-- 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;

// single register update derived from aHash
// https://github.com/tkaitchuck/aHash/blob/master/src/fallback_hash.rs#L95
//
// checksum = folded_multiple(data ^ checksum, MULTIPLE)
// folded_multiple(x, m) = HIGH(x * m) ^ LOW(x * m)

// From Knuth's PRNG
LIBC_INLINE constexpr __UINTPTR_TYPE__ MULTIPLE =
static_cast<__UINTPTR_TYPE__>(6364136223846793005ull);
Comment on lines +27 to +28
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
LIBC_INLINE constexpr __UINTPTR_TYPE__ MULTIPLE =
static_cast<__UINTPTR_TYPE__>(6364136223846793005ull);
LIBC_INLINE constexpr auto MULTIPLE =
static_cast<__UINTPTR_TYPE__>(6364136223846793005ull);

https://llvm.org/docs/CodingStandards.html#use-auto-type-deduction-to-make-code-more-readable

void initialize();
extern "C" [[gnu::cold, noreturn]] void __libc_jmpbuf_corruption();
} // namespace jmpbuf
} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_SETJMP_CHECKSUM_H
24 changes: 24 additions & 0 deletions libc/src/setjmp/i386/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
add_entrypoint_object(
setjmp
SRCS
setjmp.cpp
HDRS
../setjmp_impl.h
DEPENDS
libc.hdr.types.jmp_buf
COMPILE_OPTIONS
-O3
)

add_entrypoint_object(
longjmp
SRCS
longjmp.cpp
HDRS
../longjmp.h
DEPENDS
libc.hdr.types.jmp_buf
COMPILE_OPTIONS
-O3
Copy link
Member

Choose a reason for hiding this comment

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

Do you want to make these -O3's libc_opt_high_flag after #117638?

-fomit-frame-pointer
)
41 changes: 41 additions & 0 deletions libc/src/setjmp/i386/longjmp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===-- Implementation of longjmp (32-bit) --------------------------------===//
//
// 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/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_32)
#error "Invalid file include"
#endif

namespace LIBC_NAMESPACE_DECL {

[[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

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

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)));
}

} // namespace LIBC_NAMESPACE_DECL
43 changes: 43 additions & 0 deletions libc/src/setjmp/i386/setjmp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===-- Implementation of setjmp (32-bit) ---------------------------------===//
//
// 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 "include/llvm-libc-macros/offsetof-macro.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/setjmp/setjmp_impl.h"

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

namespace LIBC_NAMESPACE_DECL {
[[gnu::naked]]
LLVM_LIBC_FUNCTION(int, setjmp, (jmp_buf buf)) {
asm(R"(
mov 4(%%esp), %%eax

mov %%ebx, %c[ebx](%%eax)
mov %%esi, %c[esi](%%eax)
mov %%edi, %c[edi](%%eax)
mov %%ebp, %c[ebp](%%eax)

lea 4(%%esp), %%ecx
mov %%ecx, %c[esp](%%eax)

mov (%%esp), %%ecx
mov %%ecx, %c[eip](%%eax)

xorl %%eax, %%eax
retl)" ::[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))
: "eax", "ecx");
}

} // namespace LIBC_NAMESPACE_DECL
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
)
38 changes: 38 additions & 0 deletions libc/src/setjmp/linux/checksum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===-- 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() {
__UINTPTR_TYPE__ entropy[2];
syscall_impl<long>(SYS_getrandom, entropy, sizeof(entropy), 0);
// add in additional entropy
jmpbuf::value_mask ^= entropy[0];
jmpbuf::checksum_cookie ^= entropy[0];
Comment on lines +28 to +29
Copy link
Member

Choose a reason for hiding this comment

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

Did a previous version of this PR use entropy[1]? Otherwise why is entropy a 2 element array?

}

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
16 changes: 14 additions & 2 deletions libc/src/setjmp/x86_64/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
if(LIBC_CONF_SETJMP_FORTIFICATION)
set (setjmp_src setjmp_fortified.cpp)
set (longjmp_src longjmp_fortified.cpp)
else()
set (setjmp_src setjmp.cpp)
set (longjmp_src longjmp.cpp)
endif()

add_entrypoint_object(
setjmp
SRCS
setjmp.cpp
${setjmp_src}
HDRS
../setjmp_impl.h
DEPENDS
libc.hdr.types.jmp_buf
${fortification_deps}
COMPILE_OPTIONS
-O3
${fortification_defs}
)

add_entrypoint_object(
longjmp
SRCS
longjmp.cpp
${longjmp_src}
HDRS
../longjmp.h
DEPENDS
libc.hdr.types.jmp_buf
${fortification_deps}
COMPILE_OPTIONS
-O3
-fomit-frame-pointer
${fortification_defs}
)
27 changes: 2 additions & 25 deletions libc/src/setjmp/x86_64/longjmp.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===-- Implementation of longjmp -----------------------------------------===//
//===-- Implementation of longjmp (64-bit) --------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
Expand All @@ -11,34 +11,12 @@
#include "src/__support/common.h"
#include "src/__support/macros/config.h"

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

namespace LIBC_NAMESPACE_DECL {

#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

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

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"(
Expand All @@ -60,6 +38,5 @@ LLVM_LIBC_FUNCTION(void, longjmp, (jmp_buf, int)) {
[r15] "i"(offsetof(__jmp_buf, r15)), [rsp] "i"(offsetof(__jmp_buf, rsp)),
[rip] "i"(offsetof(__jmp_buf, rip)));
}
#endif

} // namespace LIBC_NAMESPACE_DECL
Loading