-
Notifications
You must be signed in to change notification settings - Fork 149
Description
I have an issue with the PropagateOnto strategy.
If I use the default PropagateOver strategy, my typestate code works well.
But when using PropagateOnto strategy, some dataflow facts vanish after invoking function.
Basically, I use an finite state machine describing pointers cannot be used after free.
The analyzed code is
#include <stdio.h>
#include <malloc.h>
int use_after_free()
{
int *p = (int *)malloc(16);
*p = 1;
free(p);
*p = 2;
return 0;
}
int main(void)
{
int (* p)() = & use_after_free;
int i = p();
return 0;
}
The IR file is
; ModuleID = 'func-pointer-use-after-free.c'
source_filename = "func-pointer-use-after-free.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @use_after_free() #0 {
entry:
%p = alloca i32*, align 8
%call = call noalias i8* @malloc(i64 noundef 16) #2
%0 = bitcast i8* %call to i32*
store i32* %0, i32** %p, align 8
%1 = load i32*, i32** %p, align 8
store i32 1, i32* %1, align 4
%2 = load i32*, i32** %p, align 8
%3 = bitcast i32* %2 to i8*
call void @free(i8* noundef %3) #2
%4 = load i32*, i32** %p, align 8
store i32 2, i32* %4, align 4
ret i32 0
}
; Function Attrs: nounwind
declare dso_local noalias i8* @malloc(i64 noundef) #1
; Function Attrs: nounwind
declare dso_local void @free(i8* noundef) #1
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%p = alloca i32 (...)*, align 8
%i = alloca i32, align 4
store i32 0, i32* %retval, align 4
store i32 (...)* bitcast (i32 ()* @use_after_free to i32 (...)*), i32 (...)** %p, align 8
%0 = load i32 (...)*, i32 (...)** %p, align 8
%call = call i32 (...) %0()
store i32 %call, i32* %i, align 4
ret i32 0
}
attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { nounwind "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #2 = { nounwind }
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"uwtable", i32 1}
!2 = !{i32 7, !"frame-pointer", i32 2}
!3 = !{!"clang version 14.0.6 (https://github.com/llvm/llvm-project.git f28c006a5895fc0e329fe15fead81e37457cb1d1)"}
The dumped results of using PropagateOver strategy is
N: %p = alloca i32*, align 8, !psr.id !4 | ID: 0
------------------------------------------------
D: Fact :{ Obj:11,state:0 } | V: BOTTOM
D: Λ | V: BOTTOM
N: %call = call noalias i8* @malloc(i64 noundef 16) #2, !psr.id !5 | ID: 1
--------------------------------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:0 } | V: BOTTOM
N: %0 = bitcast i8* %call to i32*, !psr.id !6 | ID: 2
-----------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: store i32* %0, i32** %p, align 8, !psr.id !7 | ID: 3
-------------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: %1 = load i32*, i32** %p, align 8, !psr.id !8 | ID: 4
--------------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: store i32 1, i32* %1, align 4, !psr.id !9 | ID: 5
----------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: %2 = load i32*, i32** %p, align 8, !psr.id !10 | ID: 6
---------------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: %3 = bitcast i32* %2 to i8*, !psr.id !11 | ID: 7
---------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: call void @free(i8* noundef %3) #2, !psr.id !12 | ID: 8
----------------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: %4 = load i32*, i32** %p, align 8, !psr.id !13 | ID: 9
---------------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:2 } | V: BOTTOM
N: store i32 2, i32* %4, align 4, !psr.id !14 | ID: 10
------------------------------------------------------
D: Fact :{ Obj:11,state:2 } | V: BOTTOM
D: Λ | V: BOTTOM
N: ret i32 0, !psr.id !15 | ID: 11
----------------------------------
D: Fact :{ Obj:11,state:3 } | V: BOTTOM
D: Λ | V: BOTTOM
============ Results for function 'main' ============
N: %p = alloca i32 (...)*, align 8, !psr.id !17 | ID: 13
--------------------------------------------------------
D: Fact :{ Obj:11,state:0 } | V: BOTTOM
D: Λ | V: BOTTOM
N: %i = alloca i32, align 4, !psr.id !18 | ID: 14
-------------------------------------------------
D: Λ | V: TOP
D: Fact :{ Obj:11,state:0 } | V: TOP
N: store i32 0, i32* %retval, align 4, !psr.id !19 | ID: 15
-----------------------------------------------------------
D: Fact :{ Obj:11,state:0 } | V: TOP
D: Λ | V: TOP
N: store i32 (...)* bitcast (i32 ()* @use_after_free to i32 (...)*), i32 (...)** %p, align 8, !psr.id !20 | ID: 16
------------------------------------------------------------------------------------------------------------------
D: Λ | V: TOP
D: Fact :{ Obj:11,state:0 } | V: TOP
N: %0 = load i32 (...)*, i32 (...)** %p, align 8, !psr.id !21 | ID: 17
----------------------------------------------------------------------
D: Λ | V: TOP
D: Fact :{ Obj:11,state:0 } | V: TOP
N: %call = call i32 (...) %0(), !psr.id !22 | ID: 18
----------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:0 } | V: BOTTOM
N: store i32 %call, i32* %i, align 4, !psr.id !23 | ID: 19
----------------------------------------------------------
D: Λ | V: TOP
D: Fact :{ Obj:11,state:3 } | V: TOP
N: ret i32 0, !psr.id !24 | ID: 20
----------------------------------
D: Fact :{ Obj:11,state:3 } | V: TOP
D: Λ | V: TOP
Everything is OK up to now.
But with the propagateOnto strategy, the dumped results are
N: %p = alloca i32*, align 8, !psr.id !4 | ID: 0
------------------------------------------------
D: Fact :{ Obj:11,state:0 } | V: BOTTOM
D: Λ | V: BOTTOM
N: %call = call noalias i8* @malloc(i64 noundef 16) #2, !psr.id !5 | ID: 1
--------------------------------------------------------------------------
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
D: Λ | V: BOTTOM
N: %0 = bitcast i8* %call to i32*, !psr.id !6 | ID: 2
-----------------------------------------------------
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
D: Λ | V: BOTTOM
N: store i32* %0, i32** %p, align 8, !psr.id !7 | ID: 3
-------------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: %1 = load i32*, i32** %p, align 8, !psr.id !8 | ID: 4
--------------------------------------------------------
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
D: Λ | V: BOTTOM
N: store i32 1, i32* %1, align 4, !psr.id !9 | ID: 5
----------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
N: %2 = load i32*, i32** %p, align 8, !psr.id !10 | ID: 6
---------------------------------------------------------
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
D: Λ | V: BOTTOM
N: %3 = bitcast i32* %2 to i8*, !psr.id !11 | ID: 7
---------------------------------------------------
D: Fact :{ Obj:11,state:1 } | V: BOTTOM
D: Λ | V: BOTTOM
N: call void @free(i8* noundef %3) #2, !psr.id !12 | ID: 8
----------------------------------------------------------
D: Λ | V: BOTTOM
D: Fact :{ Obj:11,state:2 } | V: BOTTOM
N: %4 = load i32*, i32** %p, align 8, !psr.id !13 | ID: 9
---------------------------------------------------------
D: Fact :{ Obj:11,state:2 } | V: BOTTOM
D: Λ | V: BOTTOM
N: store i32 2, i32* %4, align 4, !psr.id !14 | ID: 10
------------------------------------------------------
D: Fact :{ Obj:11,state:3 } | V: BOTTOM
D: Λ | V: BOTTOM
============ Results for function 'main' ============
N: %p = alloca i32 (...)*, align 8, !psr.id !17 | ID: 13
--------------------------------------------------------
D: Fact :{ Obj:11,state:0 } | V: TOP
D: Λ | V: TOP
N: %i = alloca i32, align 4, !psr.id !18 | ID: 14
-------------------------------------------------
D: Λ | V: TOP
D: Fact :{ Obj:11,state:0 } | V: TOP
N: store i32 0, i32* %retval, align 4, !psr.id !19 | ID: 15
-----------------------------------------------------------
D: Λ | V: TOP
D: Fact :{ Obj:11,state:0 } | V: TOP
N: store i32 (...)* bitcast (i32 ()* @use_after_free to i32 (...)*), i32 (...)** %p, align 8, !psr.id !20 | ID: 16
------------------------------------------------------------------------------------------------------------------
D: Λ | V: TOP
D: Fact :{ Obj:11,state:0 } | V: TOP
N: %0 = load i32 (...)*, i32 (...)** %p, align 8, !psr.id !21 | ID: 17
----------------------------------------------------------------------
D: Λ | V: TOP
D: Fact :{ Obj:11,state:0 } | V: TOP
N: %call = call i32 (...) %0(), !psr.id !22 | ID: 18
----------------------------------------------------
D: Λ | V: TOP
N: store i32 %call, i32* %i, align 4, !psr.id !23 | ID: 19
----------------------------------------------------------
D: Λ | V: TOP
As shown above, the last two instructions after calling the function have no dataflow facts.
It seems that dataflow facts do not come out from the invoked function.
In fact, I use identity flow function for all return edges.
Besides, the default PropagateOver strategy works well, so it may not be the fault of flow functions.