-
Notifications
You must be signed in to change notification settings - Fork 277
Add function pointer restriction option #5168
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
Closed
hannes-steffenhagen-diffblue
wants to merge
2
commits into
diffblue:develop
from
hannes-steffenhagen-diffblue:feature/restrict-function-pointers
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
[CPROVER Manual TOC](../../) | ||
|
||
## Restricting function pointers | ||
|
||
### Motivation | ||
|
||
CBMC comes with a way to resolve calls to function pointers to direct function | ||
calls. This is needed because symbolic execution itself can't handle calls to | ||
function pointers. In practice, this looks something like this: | ||
|
||
Given that there are functions with these signatures available in the program: | ||
|
||
``` | ||
int f(int x); | ||
int g(int x); | ||
int h(int x); | ||
``` | ||
|
||
And we have a call site like this: | ||
|
||
``` | ||
typedef int(*fptr_t)(int x); | ||
void call(fptr_t fptr) { | ||
int r = fptr(10); | ||
assert(r > 0); | ||
} | ||
``` | ||
|
||
Function pointer removal will turn this into code similar to this: | ||
|
||
``` | ||
void call(fptr_t fptr) { | ||
int r; | ||
if(fptr == &f) { | ||
r = f(10); | ||
} else if(fptr == &g) { | ||
r = g(10); | ||
} else if(fptr == &h) { | ||
r = h(10); | ||
} else { | ||
// sanity check | ||
assert(false); | ||
assume(false); | ||
} | ||
return r; | ||
} | ||
``` | ||
|
||
This works well enough for simple cases. However, this is a very simple | ||
replacement only based on the signature of the function (and whether or not they | ||
have their address taken somewhere in the program), so if there are many | ||
functions matching a particular signature, or if some of these functions are | ||
expensive in symex (e.g. functions with lots of loops or recursion), then this | ||
can be a bit cumbersome - especially if we, as a user, already know that a | ||
particular function pointer will only resolve to a single function or a small | ||
set of functions. This is what the `--restrict-function-pointer` option allows. | ||
|
||
### Example | ||
|
||
Take the motivating example. Let us assume that we know for a fact that `call` | ||
will always receive pointers to either `f` or `g` during actual executions of | ||
the program, and symbolic execution for `h` is too expensive to simply ignore | ||
the cost of its branch. For this, we will label the places in each function | ||
where function pointers are being called, to this pattern: | ||
|
||
``` | ||
<function-name>.function_pointer_call.<N> | ||
``` | ||
|
||
where `N` is referring to which function call it is - so the first call to a | ||
function pointer in a function will have `N=1`, the 5th `N=5` etc. | ||
|
||
We can call `cbmc` with `--restrict-function-pointer | ||
call.function_pointer_call.1/f,g`. This can be read as | ||
|
||
> For the first call to a function pointer in the function `call`, assume that | ||
> it can only be a call to `f` or `g` | ||
|
||
The resulting code looks similar to the original example, except now there will | ||
not be a call to `h`: | ||
|
||
``` | ||
void call(fptr_t fptr) { | ||
int r; | ||
if(fptr == &f) { | ||
r = f(10); | ||
} else if(fptr == &g) { | ||
r = g(10); | ||
} else { | ||
// sanity check | ||
assert(false); | ||
assume(false); | ||
} | ||
return r; | ||
} | ||
``` | ||
|
||
Another example: Imagine we have a simple virtual filesystem API and implementation | ||
like this: | ||
|
||
``` | ||
typedef struct filesystem_t filesystem_t; | ||
struct filesystem_t { | ||
int (*open)(filesystem_t *filesystem, const char* file_name); | ||
}; | ||
|
||
int fs_open(filesystem_t *filesystem, const char* file_name) { | ||
filesystem->open(filesystem, file_name); | ||
} | ||
|
||
int nullfs_open(filesystem_t *filesystem, const char* file_name) { | ||
return -1; | ||
} | ||
|
||
filesystem_t nullfs_val = {.open = nullfs_open}; | ||
filesystem *const nullfs = &nullfs_val; | ||
|
||
filesystem_t *get_fs_impl() { | ||
// some fancy logic to determine | ||
// which filesystem we're getting - | ||
// in-memory, backed by a database, OS file system | ||
// - but in our case, we know that | ||
// it always ends up being nullfs | ||
// for the cases we care about | ||
return nullfs; | ||
} | ||
int main(void) { | ||
filesystem_t *fs = get_fs_impl(); | ||
assert(fs_open(fs, "hello.txt") != -1); | ||
} | ||
``` | ||
|
||
In this case, the assumption is that *we* know that in our `main`, `fs` can be | ||
nothing other than `nullfs`; But perhaps due to the logic being too complicated | ||
symex ends up being unable to figure this out, so in the call to `fs_open()` we | ||
end up branching on all functions matching the signature of | ||
`filesystem_t::open`, which could be quite a few functions within the program. | ||
Worst of all, if it's address is ever taken in the program, as far as the "dumb" | ||
function pointer removal is concerned it could be `fs_open()` itself due to it | ||
having a matching signature, leading to symex being forced to follow a | ||
potentially infinite recursion until its unwind limit. | ||
|
||
In this case we can again restrict the function pointer to the value which we | ||
know it will have: | ||
|
||
``` | ||
--restrict-function-pointer fs_open.function_pointer_call.1/nullfs_open | ||
``` | ||
|
||
### Loading from file | ||
|
||
If you have many places where you want to restrict function pointers, it'd be a | ||
nuisance to have to specify them all on the command line. In these cases, you | ||
can specify a file to load the restrictions from instead, via the | ||
`--function-pointer-restrictions-file` option, which you can give the name of a | ||
JSON file with this format: | ||
|
||
``` | ||
{ | ||
"function_call_site_name": ["function1", "function2", ...], | ||
... | ||
} | ||
``` | ||
|
||
**Note:** If you pass in multiple files, or a mix of files and command line | ||
restrictions, the final restrictions will be a set union of all specified | ||
restrictions. | ||
|
||
**Note:** as of now, if something goes wrong during type checking (i.e. making | ||
sure that all function pointer replacements refer to functions in the symbol | ||
table that have the correct type), the error message will refer the command line | ||
option `--restrict-function-pointer` regardless of whether the restriction in | ||
question came from the command line or a file. |
59 changes: 59 additions & 0 deletions
59
regression/cbmc/restrict-function-pointer-to-complex-expression/test.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#include <assert.h> | ||
|
||
typedef int (*fptr_t)(int); | ||
|
||
fptr_t get_f(void); | ||
fptr_t get_g(void); | ||
|
||
void use_fg(int choice, fptr_t fptr, fptr_t gptr) | ||
{ | ||
assert((choice ? fptr : gptr)(10) == 10 + choice); | ||
} | ||
|
||
// this is just here to distinguish the behavior from FP removal, which'd include g | ||
int g_always_false_cond = 0; | ||
|
||
int main(void) | ||
{ | ||
use_fg(0, get_f(), get_g()); | ||
use_fg(1, get_f(), get_g()); | ||
} | ||
|
||
int f(int x) | ||
{ | ||
return x + 1; | ||
} | ||
|
||
int g(int x) | ||
{ | ||
return x; | ||
} | ||
|
||
int h(int x) | ||
{ | ||
return x / 2; | ||
} | ||
|
||
fptr_t get_f(void) | ||
{ | ||
if(!g_always_false_cond) | ||
{ | ||
return f; | ||
} | ||
else | ||
{ | ||
return h; | ||
} | ||
} | ||
|
||
fptr_t get_g(void) | ||
{ | ||
if(!g_always_false_cond) | ||
{ | ||
return g; | ||
} | ||
else | ||
{ | ||
return h; | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
regression/cbmc/restrict-function-pointer-to-complex-expression/test.desc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CORE | ||
test.c | ||
--restrict-function-pointer "use_fg.function_pointer_call.1/f,g" | ||
\[use_fg.assertion.2\] line \d+ assertion \(choice \? fptr : gptr\)\(10\) == 10 \+ choice: SUCCESS | ||
\[use_fg.assertion.1\] line \d+ dereferenced function pointer at use_fg.function_pointer_call.1 must be one of \[(f|g), (f|g)\]: SUCCESS | ||
^EXIT=0$ | ||
^SIGNAL=0$ |
3 changes: 3 additions & 0 deletions
3
regression/cbmc/restrict-function-pointer-to-complex-expression/test.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"use_fg.function_pointer_call.1": ["f"] | ||
} |
66 changes: 66 additions & 0 deletions
66
regression/cbmc/restrict-function-pointer-to-multiple-functions-code-check/test.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#include <assert.h> | ||
|
||
typedef int (*fptr_t)(int); | ||
|
||
fptr_t get_f(void); | ||
|
||
void use_f(fptr_t fptr) | ||
{ | ||
assert(fptr(10) >= 10); | ||
} | ||
|
||
void select_f(void); | ||
void select_g(void); | ||
void select_h(void); | ||
|
||
int main(void) | ||
{ | ||
select_f(); | ||
use_f(get_f()); | ||
select_g(); | ||
use_f(get_f()); | ||
} | ||
|
||
int f(int x) | ||
{ | ||
return x + 1; | ||
} | ||
int g(int x) | ||
{ | ||
return x; | ||
} | ||
int h(int x) | ||
{ | ||
return x - 1; | ||
} | ||
|
||
int g_select_function = 0; | ||
|
||
void select_f(void) | ||
{ | ||
g_select_function = 0; | ||
} | ||
void select_g(void) | ||
{ | ||
g_select_function = 1; | ||
} | ||
void select_h(void) | ||
{ | ||
g_select_function = 2; | ||
} | ||
|
||
fptr_t get_f(void) | ||
{ | ||
if(g_select_function == 0) | ||
{ | ||
return f; | ||
} | ||
else if(g_select_function == 1) | ||
{ | ||
return g; | ||
} | ||
else | ||
{ | ||
return h; | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
regression/cbmc/restrict-function-pointer-to-multiple-functions-code-check/test.desc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CORE | ||
test.c | ||
--restrict-function-pointer use_f.function_pointer_call.1/f,g --show-goto-functions | ||
f\(10\) | ||
g\(10\) | ||
^EXIT=0$ | ||
^SIGNAL=0$ | ||
-- | ||
h\(10\) |
68 changes: 68 additions & 0 deletions
68
regression/cbmc/restrict-function-pointer-to-multiple-functions-incorrectly/test.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#include <assert.h> | ||
|
||
typedef int (*fptr_t)(int); | ||
|
||
fptr_t get_f(void); | ||
|
||
void use_f(fptr_t fptr) | ||
{ | ||
assert(fptr(10) >= 10); | ||
} | ||
|
||
void select_f(void); | ||
void select_g(void); | ||
void select_h(void); | ||
|
||
int main(void) | ||
{ | ||
select_f(); | ||
use_f(get_f()); | ||
select_g(); | ||
use_f(get_f()); | ||
select_h(); | ||
use_f(get_f()); | ||
} | ||
|
||
int f(int x) | ||
{ | ||
return x + 1; | ||
} | ||
int g(int x) | ||
{ | ||
return x; | ||
} | ||
int h(int x) | ||
{ | ||
return x - 1; | ||
} | ||
|
||
int g_select_function = 0; | ||
|
||
void select_f(void) | ||
{ | ||
g_select_function = 0; | ||
} | ||
void select_g(void) | ||
{ | ||
g_select_function = 1; | ||
} | ||
void select_h(void) | ||
{ | ||
g_select_function = 2; | ||
} | ||
|
||
fptr_t get_f(void) | ||
{ | ||
if(g_select_function == 0) | ||
{ | ||
return f; | ||
} | ||
else if(g_select_function == 1) | ||
{ | ||
return g; | ||
} | ||
else | ||
{ | ||
return h; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe: the motivating example above?