diff options
author | David Malcolm <dmalcolm@redhat.com> | 2021-01-18 09:24:46 -0500 |
---|---|---|
committer | David Malcolm <dmalcolm@redhat.com> | 2021-01-18 09:24:46 -0500 |
commit | c7e276b869bdeb4a95735c1f037ee1a5f629de3d (patch) | |
tree | 04c4f55a4275afaf736344d1824aeccb4cbe195e /gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c | |
parent | ec153f96f8943f1d2418d2248ed219358990bb5f (diff) | |
download | gcc-c7e276b869bdeb4a95735c1f037ee1a5f629de3d.zip gcc-c7e276b869bdeb4a95735c1f037ee1a5f629de3d.tar.gz gcc-c7e276b869bdeb4a95735c1f037ee1a5f629de3d.tar.bz2 |
analyzer: use "malloc" attribute
In dce6c58db87ebf7f4477bd3126228e73e4eeee97 msebor extended the
"malloc" attribute to support user-defined allocator/deallocator
pairs.
This patch extends the "malloc" checker within -fanalyzer to use
these attributes. It is based on an earlier patch:
'RFC: add "deallocated_by" attribute for use by analyzer'
https://gcc.gnu.org/pipermail/gcc-patches/2020-October/555544.html
which added a different attribute. The patch needed a lot of reworking
to support multiple deallocators per allocator.
My hope was that this would provide a minimal level of markup that would
support library-checking without requiring lots of further markup.
I attempted to use this to detect a memory leak within a Linux
driver (CVE-2019-19078), by adding the attribute to mark these fns:
extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
extern void usb_free_urb(struct urb *urb);
where there is a leak of a "urb" on an error-handling path.
Unfortunately I ran into the problem that there are various other fns
that take "struct urb *" and the analyzer conservatively assumes that a
urb passed to them might or might not be freed and thus stops tracking
state for them.
Hence this will only detect issues for the simplest cases (without
adding another attribute).
gcc/analyzer/ChangeLog:
* analyzer.h (is_std_named_call_p): New decl.
* diagnostic-manager.cc (path_builder::get_sm): New.
(state_change_event_creator::state_change_event_creator): Add "pb"
param.
(state_change_event_creator::on_global_state_change): Don't consider
state changes affecting other state_machines.
(state_change_event_creator::on_state_change): Likewise.
(state_change_event_creator::m_pb): New field.
(diagnostic_manager::add_events_for_eedge): Pass pb to visitor
ctor.
* region-model-impl-calls.cc
(region_model::impl_deallocation_call): New.
* region-model.cc: Include "attribs.h".
(region_model::on_call_post): Handle fndecls referenced by
__attribute__((deallocated_by(FOO))).
* region-model.h (region_model::impl_deallocation_call): New decl.
* sm-malloc.cc: Include "stringpool.h" and "attribs.h". Add
leading comment.
(class api): Delete.
(enum resource_state): Update comment for change from api to
deallocator and deallocator_set.
(allocation_state::allocation_state): Drop api param. Add
"deallocators" and "deallocator".
(allocation_state::m_api): Drop field in favor of...
(allocation_state::m_deallocators): New field.
(allocation_state::m_deallocator): New field.
(enum wording): Add WORDING_DEALLOCATED.
(struct deallocator): New.
(struct standard_deallocator): New.
(struct custom_deallocator): New.
(struct deallocator_set): New.
(struct custom_deallocator_set): New.
(struct standard_deallocator_set): New.
(struct deallocator_set_map_traits): New.
(malloc_state_machine::m_malloc): Drop field
(malloc_state_machine::m_scalar_new): Likewise.
(malloc_state_machine::m_vector_new): Likewise.
(malloc_state_machine::m_free): New field
(malloc_state_machine::m_scalar_delete): Likewise.
(malloc_state_machine::m_vector_delete): Likewise.
(malloc_state_machine::deallocator_map_t): New typedef.
(malloc_state_machine::m_deallocator_map): New field.
(malloc_state_machine::deallocator_set_cache_t): New typedef.
(malloc_state_machine::m_custom_deallocator_set_cache): New field.
(malloc_state_machine::custom_deallocator_set_map_t): New typedef.
(malloc_state_machine::m_custom_deallocator_set_map): New field.
(malloc_state_machine::m_dynamic_sets): New field.
(malloc_state_machine::m_dynamic_deallocators): New field.
(api::api): Delete.
(deallocator::deallocator): New ctor.
(deallocator::hash): New.
(deallocator::dump_to_pp): New.
(deallocator::cmp): New.
(deallocator::cmp_ptr_ptr): New.
(standard_deallocator::standard_deallocator): New ctor.
(deallocator_set::deallocator_set): New ctor.
(deallocator_set::dump): New.
(custom_deallocator_set::custom_deallocator_set): New ctor.
(custom_deallocator_set::contains_p): New.
(custom_deallocator_set::maybe_get_single): New.
(custom_deallocator_set::dump_to_pp): New.
(standard_deallocator_set::standard_deallocator_set): New ctor.
(standard_deallocator_set::contains_p): New.
(standard_deallocator_set::maybe_get_single): New.
(standard_deallocator_set::dump_to_pp): New.
(start_p): New.
(class mismatching_deallocation): Update for conversion from api
to deallocator_set and deallocator.
(double_free::emit): Use %qs.
(class use_after_free): Update for conversion from api to
deallocator_set and deallocator.
(malloc_leak::describe_state_change): Only emit "allocated here" on
a start->nonnull transition, rather than on other transitions to
nonnull.
(allocation_state::dump_to_pp): Update for conversion from api to
deallocator_set.
(allocation_state::get_nonnull): Likewise.
(malloc_state_machine::malloc_state_machine): Likewise.
(malloc_state_machine::~malloc_state_machine): New.
(malloc_state_machine::add_state): Update for conversion from api
to deallocator_set.
(malloc_state_machine::get_or_create_custom_deallocator_set): New.
(malloc_state_machine::maybe_create_custom_deallocator_set): New.
(malloc_state_machine::get_or_create_deallocator): New.
(malloc_state_machine::on_stmt): Update for conversion from api
to deallocator_set. Handle "__attribute__((malloc(FOO)))", and
the special attribute set on FOO.
(malloc_state_machine::on_allocator_call): Update for conversion
from api to deallocator_set. Add "returns_nonnull" param and use
it to affect which state to transition to.
(malloc_state_machine::on_deallocator_call): Update for conversion
from api to deallocator_set.
gcc/ChangeLog:
* attribs.h (fndecl_dealloc_argno): New decl.
* builtins.c (call_dealloc_argno): Split out second half of
function into...
(fndecl_dealloc_argno): New.
* doc/extend.texi (Common Function Attributes): Document the
interaction between the analyzer and the malloc attribute.
* doc/invoke.texi (Static Analyzer Options): Likewise.
gcc/testsuite/ChangeLog:
* gcc.dg/analyzer/attr-malloc-1.c: New test.
* gcc.dg/analyzer/attr-malloc-2.c: New test.
* gcc.dg/analyzer/attr-malloc-4.c: New test.
* gcc.dg/analyzer/attr-malloc-5.c: New test.
* gcc.dg/analyzer/attr-malloc-6.c: New test.
* gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c: New test.
* gcc.dg/analyzer/attr-malloc-misuses.c: New test.
Diffstat (limited to 'gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c')
-rw-r--r-- | gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c | 75 |
1 files changed, 75 insertions, 0 deletions
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c new file mode 100644 index 0000000..3de32b1 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c @@ -0,0 +1,75 @@ +extern void free (void *); + +struct foo +{ + int m_int; +}; + +extern void foo_release (struct foo *); +extern struct foo *foo_acquire (void) + __attribute__ ((malloc (foo_release))); +extern void use_foo (const struct foo *) + __attribute__((nonnull)); + +void test_1 (void) +{ + struct foo *p = foo_acquire (); + foo_release (p); +} + +void test_2 (void) +{ + struct foo *p = foo_acquire (); /* { dg-message "this call could return NULL" } */ + p->m_int = 42; /* { dg-warning "dereference of possibly-NULL 'p'" } */ + foo_release (p); +} + +void test_2a (void) +{ + struct foo *p = foo_acquire (); /* { dg-message "this call could return NULL" } */ + use_foo (p); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */ + foo_release (p); +} + +void test_3 (void) +{ + struct foo *p = foo_acquire (); /* { dg-message "allocated here" } */ +} /* { dg-warning "leak of 'p'" } */ + +void test_4 (struct foo *p) +{ + foo_release (p); + foo_release (p); /* { dg-warning "double-'foo_release' of 'p'" } */ +} + +void test_4a (void) +{ + struct foo *p = foo_acquire (); + foo_release (p); + foo_release (p); /* { dg-warning "double-'foo_release' of 'p'" } */ +} + +void test_5 (void) +{ + struct foo *p = foo_acquire (); /* { dg-message "allocated here \\(expects deallocation with 'foo_release'\\)" } */ + free (p); /* { dg-warning "'p' should have been deallocated with 'foo_release' but was deallocated with 'free'" } */ +} + +void test_6 (struct foo *p) +{ + foo_release (p); + free (p); // TODO: double-release warning! +} + +void test_7 () +{ + struct foo f; + foo_release (&f); /* { dg-warning "not on the heap" "analyzer" } */ + /* { dg-warning "'foo_release' called on unallocated object 'f'" "non-analyzer" { target *-*-* } .-1 } */ +} + +int test_8 (struct foo *p) +{ + foo_release (p); + return p->m_int; /* { dg-warning "use after 'foo_release' of 'p'" } */ +} |