Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
#include <stdio.h>
#include "soft_trap_runtime_impl.h"

// `noinline` here is used to prevent inlining which can indirectly lead to
// LLVM figuring out UB is happening during soft traps and emit a hard trap
// after the soft trap call. This is a workaround for rdar://183581715
// lower-trap-merged{bad_read}
// upper-trap-merged{bad_read}
int bad_read(int *__bidi_indexable ptr, int idx) {
__attribute__((noinline)) int bad_read(int *__bidi_indexable ptr, int idx) {
// lower-trap@+2{indexing below lower bound in 'ptr[idx]'}
// upper-trap@+1{indexing above upper bound in 'ptr[idx]'}
return ptr[idx];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// This is a variant of `lower_upper_check` that requests inlining of `bad_read`
// that can lead to LLVM discovering UB in soft trap mode.
// RUN: %clang_bsafe %s -o %t
// RUN: %expect-no-trap %t
// RUN: %expect-trap --verify-prefix=lower-trap %s %t arg1
// RUN: %expect-trap --verify-prefix=upper-trap %s %t arg1 arg2
// FIXME: With soft-traps in optimized builds LLVM can detect that the lower bound
// access leads to UB and emits a trap (rdar://183581715).
// XFAIL: soft-traps && optimized
#include <ptrcheck.h>
#include <stdio.h>
#include "soft_trap_runtime_impl.h"

// lower-trap-merged{bad_read}
// upper-trap-merged{bad_read}
__attribute__((always_inline)) int bad_read(int *__bidi_indexable ptr, int idx) {
// lower-trap@+2{indexing below lower bound in 'ptr[idx]'}
// upper-trap@+1{indexing above upper bound in 'ptr[idx]'}
return ptr[idx];
}

int main(int argc, const char **__counted_by(argc) argv) {
int pad;
int local[] = {0, 1};
int pad2;
int result = 0;
if (argc == 1) {
result = bad_read(local, 1);
} else if (argc == 2) {
result = bad_read(local, -1);
} else {
result = bad_read(local, 2);
}
printf("result: %d\n", result);
return 0;
}
57 changes: 53 additions & 4 deletions compiler-rt/test/bounds_safety/scripts/expect_trap.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,44 @@ def check_trap(lldb, process, expectation, verify_source, merged_traps=False,
return 0


def resume_and_expect_clean_exit(lldb, process):
"""Resume a process stopped at a soft trap and require a clean exit.

Soft traps are non-fatal: after the trap is validated the process must run
to completion and exit with status 0. This is what distinguishes a soft
trap from a hard trap and is what catches miscompiles that turn a soft trap
into a fatal one. Assumes exactly one soft trap fires before exit (we do not
yet support multiple verify expectations); a second stop is reported as a
failure rather than silently passing.
"""
# LLDBProcessContextManager runs in synchronous mode (SetAsync(False)), so
# Continue() blocks until the process next stops or exits.
error = process.Continue()
if error.Fail():
log.error("failed to resume process after soft trap: %s", error)
return 1

state = process.GetState()
if state != lldb.eStateExited:
log.error(
"process did not exit after soft trap (state=%s); the soft trap "
"may have become fatal", state,
)
log_stop_info(process.GetSelectedThread())
return 1

exit_status = process.GetExitStatus()
if exit_status != 0:
log.error(
"process exited with status %d after soft trap (expected 0)",
exit_status,
)
return 1

log.info("Process exited cleanly after soft trap")
return 0


def run_debugger(binary, args, lldb_python_path, verify_source, verify_prefix,
merged_traps=False, soft_traps=False):
# Parse and validate verify comments before launching the debugger so we
Expand Down Expand Up @@ -513,10 +551,21 @@ def run_debugger(binary, args, lldb_python_path, verify_source, verify_prefix,
return 1

if soft_traps and not has_plugin:
return check_soft_trap_no_plugin(lldb, ctx.process, expectation,
verify_source)
return check_trap(lldb, ctx.process, expectation, verify_source,
merged_traps, soft_traps)
rc = check_soft_trap_no_plugin(lldb, ctx.process, expectation,
verify_source)
else:
rc = check_trap(lldb, ctx.process, expectation, verify_source,
merged_traps, soft_traps)
if rc != 0:
return rc

# Hard/merged traps: stopping at the trap is the entire check.
if not soft_traps:
return 0

# Soft traps are non-fatal: after validating the trap the process must
# resume and run to a clean exit.
return resume_and_expect_clean_exit(lldb, ctx.process)


def main():
Expand Down