Protection against stack-based memory corruption errors using SafeStack
This patch adds the safe stack instrumentation pass to LLVM, which separates
the program stack into a safe stack, which stores return addresses, register
spills, and local variables that are statically verified to be accessed
in a safe way, and the unsafe stack, which stores everything else. Such
separation makes it much harder for an attacker to corrupt objects on the
safe stack, including function pointers stored in spilled registers and
return addresses. You can find more information about the safe stack, as
well as other parts of or control-flow hijack protection technique in our
OSDI paper on code-pointer integrity (http://dslab.epfl.ch/pubs/cpi.pdf)
and our project website (http://levee.epfl.ch).
The overhead of our implementation of the safe stack is very close to zero
(0.01% on the Phoronix benchmarks). This is lower than the overhead of
stack cookies, which are supported by LLVM and are commonly used today,
yet the security guarantees of the safe stack are strictly stronger than
stack cookies. In some cases, the safe stack improves performance due to
better cache locality.
Our current implementation of the safe stack is stable and robust, we
used it to recompile multiple projects on Linux including Chromium, and
we also recompiled the entire FreeBSD user-space system and more than 100
packages. We ran unit tests on the FreeBSD system and many of the packages
and observed no errors caused by the safe stack. The safe stack is also fully
binary compatible with non-instrumented code and can be applied to parts of
a program selectively.
This patch is our implementation of the safe stack on top of LLVM. The
patches make the following changes:
- Add the safestack function attribute, similar to the ssp, sspstrong and
sspreq attributes.
- Add the SafeStack instrumentation pass that applies the safe stack to all
functions that have the safestack attribute. This pass moves all unsafe local
variables to the unsafe stack with a separate stack pointer, whereas all
safe variables remain on the regular stack that is managed by LLVM as usual.
- Invoke the pass as the last stage before code generation (at the same time
the existing cookie-based stack protector pass is invoked).
- Add unit tests for the safe stack.
Original patch by Volodymyr Kuznetsov and others at the Dependable Systems
Lab at EPFL; updates and upstreaming by myself.
Differential Revision: http://reviews.llvm.org/D6094
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@239761 91177308-0d34-0410-b5e6-96231b3b80d8
2015-06-15 21:07:11 +00:00
|
|
|
; RUN: opt -safe-stack -S -mtriple=i386-pc-linux-gnu < %s -o - | FileCheck %s
|
|
|
|
; RUN: opt -safe-stack -S -mtriple=x86_64-pc-linux-gnu < %s -o - | FileCheck %s
|
|
|
|
|
|
|
|
@.str = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1
|
|
|
|
|
|
|
|
; no arrays / no nested arrays
|
|
|
|
; Requires no protector.
|
|
|
|
|
|
|
|
define void @foo(i8* %a) nounwind uwtable safestack {
|
|
|
|
entry:
|
2015-11-13 21:21:42 +00:00
|
|
|
; CHECK-LABEL: define void @foo(
|
Protection against stack-based memory corruption errors using SafeStack
This patch adds the safe stack instrumentation pass to LLVM, which separates
the program stack into a safe stack, which stores return addresses, register
spills, and local variables that are statically verified to be accessed
in a safe way, and the unsafe stack, which stores everything else. Such
separation makes it much harder for an attacker to corrupt objects on the
safe stack, including function pointers stored in spilled registers and
return addresses. You can find more information about the safe stack, as
well as other parts of or control-flow hijack protection technique in our
OSDI paper on code-pointer integrity (http://dslab.epfl.ch/pubs/cpi.pdf)
and our project website (http://levee.epfl.ch).
The overhead of our implementation of the safe stack is very close to zero
(0.01% on the Phoronix benchmarks). This is lower than the overhead of
stack cookies, which are supported by LLVM and are commonly used today,
yet the security guarantees of the safe stack are strictly stronger than
stack cookies. In some cases, the safe stack improves performance due to
better cache locality.
Our current implementation of the safe stack is stable and robust, we
used it to recompile multiple projects on Linux including Chromium, and
we also recompiled the entire FreeBSD user-space system and more than 100
packages. We ran unit tests on the FreeBSD system and many of the packages
and observed no errors caused by the safe stack. The safe stack is also fully
binary compatible with non-instrumented code and can be applied to parts of
a program selectively.
This patch is our implementation of the safe stack on top of LLVM. The
patches make the following changes:
- Add the safestack function attribute, similar to the ssp, sspstrong and
sspreq attributes.
- Add the SafeStack instrumentation pass that applies the safe stack to all
functions that have the safestack attribute. This pass moves all unsafe local
variables to the unsafe stack with a separate stack pointer, whereas all
safe variables remain on the regular stack that is managed by LLVM as usual.
- Invoke the pass as the last stage before code generation (at the same time
the existing cookie-based stack protector pass is invoked).
- Add unit tests for the safe stack.
Original patch by Volodymyr Kuznetsov and others at the Dependable Systems
Lab at EPFL; updates and upstreaming by myself.
Differential Revision: http://reviews.llvm.org/D6094
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@239761 91177308-0d34-0410-b5e6-96231b3b80d8
2015-06-15 21:07:11 +00:00
|
|
|
; CHECK-NOT: __safestack_unsafe_stack_ptr
|
2015-11-13 21:21:42 +00:00
|
|
|
; CHECK: ret void
|
Protection against stack-based memory corruption errors using SafeStack
This patch adds the safe stack instrumentation pass to LLVM, which separates
the program stack into a safe stack, which stores return addresses, register
spills, and local variables that are statically verified to be accessed
in a safe way, and the unsafe stack, which stores everything else. Such
separation makes it much harder for an attacker to corrupt objects on the
safe stack, including function pointers stored in spilled registers and
return addresses. You can find more information about the safe stack, as
well as other parts of or control-flow hijack protection technique in our
OSDI paper on code-pointer integrity (http://dslab.epfl.ch/pubs/cpi.pdf)
and our project website (http://levee.epfl.ch).
The overhead of our implementation of the safe stack is very close to zero
(0.01% on the Phoronix benchmarks). This is lower than the overhead of
stack cookies, which are supported by LLVM and are commonly used today,
yet the security guarantees of the safe stack are strictly stronger than
stack cookies. In some cases, the safe stack improves performance due to
better cache locality.
Our current implementation of the safe stack is stable and robust, we
used it to recompile multiple projects on Linux including Chromium, and
we also recompiled the entire FreeBSD user-space system and more than 100
packages. We ran unit tests on the FreeBSD system and many of the packages
and observed no errors caused by the safe stack. The safe stack is also fully
binary compatible with non-instrumented code and can be applied to parts of
a program selectively.
This patch is our implementation of the safe stack on top of LLVM. The
patches make the following changes:
- Add the safestack function attribute, similar to the ssp, sspstrong and
sspreq attributes.
- Add the SafeStack instrumentation pass that applies the safe stack to all
functions that have the safestack attribute. This pass moves all unsafe local
variables to the unsafe stack with a separate stack pointer, whereas all
safe variables remain on the regular stack that is managed by LLVM as usual.
- Invoke the pass as the last stage before code generation (at the same time
the existing cookie-based stack protector pass is invoked).
- Add unit tests for the safe stack.
Original patch by Volodymyr Kuznetsov and others at the Dependable Systems
Lab at EPFL; updates and upstreaming by myself.
Differential Revision: http://reviews.llvm.org/D6094
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@239761 91177308-0d34-0410-b5e6-96231b3b80d8
2015-06-15 21:07:11 +00:00
|
|
|
%a.addr = alloca i8*, align 8
|
|
|
|
store i8* %a, i8** %a.addr, align 8
|
|
|
|
%0 = load i8*, i8** %a.addr, align 8
|
|
|
|
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0), i8* %0)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
declare i32 @printf(i8*, ...)
|
2015-11-13 21:21:42 +00:00
|
|
|
|
|
|
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
|
|
|
target triple = "x86_64-unknown-linux-gnu"
|
|
|
|
|
|
|
|
define void @call_memset(i64 %len) safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_memset
|
|
|
|
; CHECK: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
|
|
|
|
call void @llvm.memset.p0i8.i64(i8* %arraydecay, i8 1, i64 %len, i32 1, i1 false)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
define void @call_constant_memset() safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_constant_memset
|
|
|
|
; CHECK-NOT: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 2
|
|
|
|
call void @llvm.memset.p0i8.i64(i8* %arraydecay, i8 1, i64 7, i32 1, i1 false)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
define void @call_constant_overflow_memset() safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_constant_overflow_memset
|
|
|
|
; CHECK: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 7
|
|
|
|
call void @llvm.memset.p0i8.i64(i8* %arraydecay, i8 1, i64 5, i32 1, i1 false)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
define void @call_constant_underflow_memset() safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_constant_underflow_memset
|
|
|
|
; CHECK: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr [10 x i8], [10 x i8]* %q, i32 0, i32 -1
|
|
|
|
call void @llvm.memset.p0i8.i64(i8* %arraydecay, i8 1, i64 3, i32 1, i1 false)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
; Readnone nocapture -> safe
|
|
|
|
define void @call_readnone(i64 %len) safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_readnone
|
|
|
|
; CHECK-NOT: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
|
|
|
|
call void @readnone(i8* %arraydecay)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
; Arg0 is readnone, arg1 is not. Pass alloca ptr as arg0 -> safe
|
|
|
|
define void @call_readnone0_0(i64 %len) safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_readnone0_0
|
|
|
|
; CHECK-NOT: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
|
|
|
|
call void @readnone0(i8* %arraydecay, i8* zeroinitializer)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
; Arg0 is readnone, arg1 is not. Pass alloca ptr as arg1 -> unsafe
|
|
|
|
define void @call_readnone0_1(i64 %len) safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_readnone0_1
|
|
|
|
; CHECK: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
|
|
|
|
call void @readnone0(i8 *zeroinitializer, i8* %arraydecay)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
; Readonly nocapture -> unsafe
|
|
|
|
define void @call_readonly(i64 %len) safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_readonly
|
|
|
|
; CHECK: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
|
|
|
|
call void @readonly(i8* %arraydecay)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
; Readonly nocapture -> unsafe
|
|
|
|
define void @call_arg_readonly(i64 %len) safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_arg_readonly
|
|
|
|
; CHECK: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
|
|
|
|
call void @arg_readonly(i8* %arraydecay)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
; Readwrite nocapture -> unsafe
|
|
|
|
define void @call_readwrite(i64 %len) safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_readwrite
|
|
|
|
; CHECK: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
|
|
|
|
call void @readwrite(i8* %arraydecay)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
; Captures the argument -> unsafe
|
|
|
|
define void @call_capture(i64 %len) safestack {
|
|
|
|
entry:
|
|
|
|
; CHECK-LABEL: define void @call_capture
|
|
|
|
; CHECK: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
%q = alloca [10 x i8], align 1
|
|
|
|
%arraydecay = getelementptr inbounds [10 x i8], [10 x i8]* %q, i32 0, i32 0
|
|
|
|
call void @capture(i8* %arraydecay)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
; Lifetime intrinsics are always safe.
|
|
|
|
define void @call_lifetime(i32* %p) {
|
|
|
|
; CHECK-LABEL: define void @call_lifetime
|
|
|
|
; CHECK-NOT: @__safestack_unsafe_stack_ptr
|
|
|
|
; CHECK: ret void
|
|
|
|
entry:
|
|
|
|
%q = alloca [100 x i8], align 16
|
|
|
|
%0 = bitcast [100 x i8]* %q to i8*
|
|
|
|
call void @llvm.lifetime.start(i64 100, i8* %0)
|
|
|
|
call void @llvm.lifetime.end(i64 100, i8* %0)
|
|
|
|
ret void
|
|
|
|
}
|
|
|
|
|
|
|
|
declare void @readonly(i8* nocapture) readonly
|
|
|
|
declare void @arg_readonly(i8* readonly nocapture)
|
|
|
|
declare void @readwrite(i8* nocapture)
|
|
|
|
declare void @capture(i8* readnone) readnone
|
|
|
|
|
|
|
|
declare void @readnone(i8* nocapture) readnone
|
|
|
|
declare void @readnone0(i8* nocapture readnone, i8* nocapture)
|
|
|
|
|
|
|
|
declare void @llvm.memset.p0i8.i64(i8* nocapture, i8, i64, i32, i1) nounwind argmemonly
|
|
|
|
|
|
|
|
declare void @llvm.lifetime.start(i64, i8* nocapture) nounwind argmemonly
|
|
|
|
declare void @llvm.lifetime.end(i64, i8* nocapture) nounwind argmemonly
|