diff options
Diffstat (limited to 'gcc')
-rw-r--r-- | gcc/analyzer/analyzer.h | 1 | ||||
-rw-r--r-- | gcc/analyzer/diagnostic-manager.cc | 15 | ||||
-rw-r--r-- | gcc/analyzer/region-model-impl-calls.cc | 9 | ||||
-rw-r--r-- | gcc/analyzer/region-model.cc | 9 | ||||
-rw-r--r-- | gcc/analyzer/region-model.h | 1 | ||||
-rw-r--r-- | gcc/analyzer/sm-malloc.cc | 767 | ||||
-rw-r--r-- | gcc/attribs.h | 2 | ||||
-rw-r--r-- | gcc/builtins.c | 10 | ||||
-rw-r--r-- | gcc/doc/extend.texi | 52 | ||||
-rw-r--r-- | gcc/doc/invoke.texi | 14 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c | 75 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/analyzer/attr-malloc-2.c | 24 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/analyzer/attr-malloc-4.c | 21 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/analyzer/attr-malloc-5.c | 12 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c | 228 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c | 224 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/analyzer/attr-malloc-misuses.c | 18 |
17 files changed, 1354 insertions, 128 deletions
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 6996092..f50ac66 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -205,6 +205,7 @@ extern bool is_special_named_call_p (const gcall *call, const char *funcname, extern bool is_named_call_p (tree fndecl, const char *funcname); extern bool is_named_call_p (tree fndecl, const char *funcname, const gcall *call, unsigned int num_args); +extern bool is_std_named_call_p (tree fndecl, const char *funcname); extern bool is_std_named_call_p (tree fndecl, const char *funcname, const gcall *call, unsigned int num_args); extern bool is_setjmp_call_p (const gcall *call); diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 3625e3f..22ca024 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -189,6 +189,8 @@ public: return m_feasibility_problem; } + const state_machine *get_sm () const { return m_sd.m_sm; } + private: typedef reachability<eg_traits> enode_reachability; @@ -709,9 +711,11 @@ diagnostic_manager::build_emission_path (const path_builder &pb, class state_change_event_creator : public state_change_visitor { public: - state_change_event_creator (const exploded_edge &eedge, + state_change_event_creator (const path_builder &pb, + const exploded_edge &eedge, checker_path *emission_path) - : m_eedge (eedge), + : m_pb (pb), + m_eedge (eedge), m_emission_path (emission_path) {} @@ -720,6 +724,8 @@ public: state_machine::state_t dst_sm_val) FINAL OVERRIDE { + if (&sm != m_pb.get_sm ()) + return false; const exploded_node *src_node = m_eedge.m_src; const program_point &src_point = src_node->get_point (); const int src_stack_depth = src_point.get_stack_depth (); @@ -748,6 +754,8 @@ public: const svalue *sval, const svalue *dst_origin_sval) FINAL OVERRIDE { + if (&sm != m_pb.get_sm ()) + return false; const exploded_node *src_node = m_eedge.m_src; const program_point &src_point = src_node->get_point (); const int src_stack_depth = src_point.get_stack_depth (); @@ -783,6 +791,7 @@ public: return false; } + const path_builder &m_pb; const exploded_edge &m_eedge; checker_path *m_emission_path; }; @@ -1002,7 +1011,7 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb, | ~~~~~~~~~~~~~^~~~~ | | | (3) ...to here (end_cfg_edge_event). */ - state_change_event_creator visitor (eedge, emission_path); + state_change_event_creator visitor (pb, eedge, emission_path); for_each_state_change (src_state, dst_state, pb.get_ext_state (), &visitor); diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 1b81c74..d3b66ba 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -436,6 +436,15 @@ region_model::impl_call_strlen (const call_details &cd) return true; } +/* Handle calls to functions referenced by + __attribute__((malloc(FOO))). */ + +void +region_model::impl_deallocation_call (const call_details &cd) +{ + impl_call_free (cd); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 81e1a48..cfe8a39 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -65,6 +65,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/region-model-reachability.h" #include "analyzer/analyzer-selftests.h" #include "stor-layout.h" +#include "attribs.h" #if ENABLE_ANALYZER @@ -917,6 +918,14 @@ region_model::on_call_post (const gcall *call, impl_call_operator_delete (cd); return; } + /* Was this fndecl referenced by + __attribute__((malloc(FOO)))? */ + if (lookup_attribute ("*dealloc", DECL_ATTRIBUTES (callee_fndecl))) + { + call_details cd (call, this, ctxt); + impl_deallocation_call (cd); + return; + } } if (unknown_side_effects) diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 0e303d0..efd1a09 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -463,6 +463,7 @@ class region_model bool impl_call_strlen (const call_details &cd); bool impl_call_operator_new (const call_details &cd); bool impl_call_operator_delete (const call_details &cd); + void impl_deallocation_call (const call_details &cd); void handle_unrecognized_call (const gcall *call, region_model_context *ctxt); diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index e1fea02..d7c76e3 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -42,6 +42,8 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/program-point.h" #include "analyzer/store.h" #include "analyzer/region-model.h" +#include "stringpool.h" +#include "attribs.h" #if ENABLE_ANALYZER @@ -49,14 +51,38 @@ namespace ana { namespace { -class api; +/* This state machine and its various support classes track allocations + and deallocations. + + It has a few standard allocation/deallocation pairs (e.g. new/delete), + and also supports user-defined ones via + __attribute__ ((malloc(DEALLOCATOR))). + + There can be more than one valid deallocator for a given allocator, + for example: + __attribute__ ((malloc (fclose))) + __attribute__ ((malloc (freopen, 3))) + FILE* fopen (const char*, const char*); + A deallocator_set represents a particular set of valid deallocators. + + We track the expected deallocator_set for a value, but not the allocation + function - there could be more than one allocator per deallocator_set. + For example, there could be dozens of allocators for "free" beyond just + malloc e.g. calloc, xstrdup, etc. We don't want to explode the number + of states by tracking individual allocators in the exploded graph; + we merely want to track "this value expects to have 'free' called on it". + Perhaps we can reconstruct which allocator was used later, when emitting + the path, if it's necessary for precision of wording of diagnostics. */ + +class deallocator; +class deallocator_set; class malloc_state_machine; /* An enum for discriminating between different kinds of allocation_state. */ enum resource_state { - /* States that are independent of api. */ + /* States that are independent of allocator/deallocator. */ /* The start state. */ RS_START, @@ -71,28 +97,33 @@ enum resource_state /* Stop state, for pointers we don't want to track any more. */ RS_STOP, - /* States that relate to a specific api. */ + /* States that relate to a specific deallocator_set. */ - /* State for a pointer returned from the api's allocator that hasn't + /* State for a pointer returned from an allocator that hasn't been checked for NULL. It could be a pointer to heap-allocated memory, or could be NULL. */ RS_UNCHECKED, - /* State for a pointer returned from the api's allocator, + /* State for a pointer returned from an allocator, known to be non-NULL. */ RS_NONNULL, - /* State for a pointer passed to the api's deallocator. */ + /* State for a pointer passed to a deallocator. */ RS_FREED }; -/* Custom state subclass, which can optionally refer to an an api. */ +/* Custom state subclass, which can optionally refer to an a + deallocator_set. */ struct allocation_state : public state_machine::state { allocation_state (const char *name, unsigned id, - enum resource_state rs, const api *a) - : state (name, id), m_rs (rs), m_api (a) + enum resource_state rs, + const deallocator_set *deallocators, + const deallocator *deallocator) + : state (name, id), m_rs (rs), + m_deallocators (deallocators), + m_deallocator (deallocator) {} void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE; @@ -100,7 +131,8 @@ struct allocation_state : public state_machine::state const allocation_state *get_nonnull () const; enum resource_state m_rs; - const api *m_api; + const deallocator_set *m_deallocators; + const deallocator *m_deallocator; }; /* An enum for choosing which wording to use in various diagnostics @@ -109,52 +141,186 @@ struct allocation_state : public state_machine::state enum wording { WORDING_FREED, - WORDING_DELETED + WORDING_DELETED, + WORDING_DEALLOCATED }; -/* Represents a particular family of API calls for allocating/deallocating - heap memory that should be matched e.g. - - malloc/free - - scalar new/delete - - vector new[]/delete[] - etc. +/* Base class representing a deallocation function, + either a built-in one we know about, or one exposed via + __attribute__((malloc(DEALLOCATOR))). */ - We track the expected deallocation function, but not the allocation - function - there could be more than one allocator per deallocator. For - example, there could be dozens of allocators for "free" beyond just - malloc e.g. calloc, xstrdup, etc. We don't want to explode the number - of states by tracking individual allocators in the exploded graph; - we merely want to track "this value expects to have 'free' called on it". - Perhaps we can reconstruct which allocator was used later, when emitting - the path, if it's necessary for precision of wording of diagnostics. */ - -struct api +struct deallocator { - api (malloc_state_machine *sm, - const char *name, - const char *dealloc_funcname, - enum wording wording); + hashval_t hash () const; + void dump_to_pp (pretty_printer *pp) const; + static int cmp (const deallocator *a, const deallocator *b); + static int cmp_ptr_ptr (const void *, const void *); - /* An internal name for identifying this API in dumps. */ + /* Name to use in diagnostics. */ const char *m_name; - /* The name of the deallocation function, for use in diagnostics. */ - const char *m_dealloc_funcname; + /* Which wording to use in diagnostics. */ + enum wording m_wording; + + /* State for a value passed to one of the deallocators. */ + state_machine::state_t m_freed; + +protected: + deallocator (malloc_state_machine *sm, + const char *name, + enum wording wording); +}; + +/* Subclass representing a predefined deallocator. + e.g. "delete []", without needing a specific FUNCTION_DECL + ahead of time. */ + +struct standard_deallocator : public deallocator +{ + standard_deallocator (malloc_state_machine *sm, + const char *name, + enum wording wording); +}; + +/* Subclass representing a user-defined deallocator + via __attribute__((malloc(DEALLOCATOR))) given + a specific FUNCTION_DECL. */ + +struct custom_deallocator : public deallocator +{ + custom_deallocator (malloc_state_machine *sm, + tree deallocator_fndecl, + enum wording wording) + : deallocator (sm, IDENTIFIER_POINTER (DECL_NAME (deallocator_fndecl)), + wording) + { + } +}; + +/* Base class representing a set of possible deallocators. + Often this will be just a single deallocator, but some + allocators have multiple valid deallocators (e.g. the result of + "fopen" can be closed by either "fclose" or "freopen"). */ + +struct deallocator_set +{ + deallocator_set (malloc_state_machine *sm, + enum wording wording); + virtual ~deallocator_set () {} + + virtual bool contains_p (const deallocator *d) const = 0; + virtual const deallocator *maybe_get_single () const = 0; + virtual void dump_to_pp (pretty_printer *pp) const = 0; + void dump () const; /* Which wording to use in diagnostics. */ enum wording m_wording; - /* Pointers to api-specific states. + /* Pointers to states. These states are owned by the state_machine base class. */ - /* State for an unchecked result from this api's allocator. */ + /* State for an unchecked result from an allocator using this set. */ state_machine::state_t m_unchecked; - /* State for a known non-NULL result from this apis's allocator. */ + /* State for a known non-NULL result from such an allocator. */ state_machine::state_t m_nonnull; +}; - /* State for a value passed to this api's deallocator. */ - state_machine::state_t m_freed; +/* Subclass of deallocator_set representing a set of deallocators + defined by one or more __attribute__((malloc(DEALLOCATOR))). */ + +struct custom_deallocator_set : public deallocator_set +{ + typedef const auto_vec <const deallocator *> *key_t; + + custom_deallocator_set (malloc_state_machine *sm, + const auto_vec <const deallocator *> *vec, + //const char *name, + //const char *dealloc_funcname, + //unsigned arg_idx, + enum wording wording); + + bool contains_p (const deallocator *d) const FINAL OVERRIDE; + const deallocator *maybe_get_single () const FINAL OVERRIDE; + void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE; + + auto_vec <const deallocator *> m_deallocator_vec; +}; + +/* Subclass of deallocator_set representing a set of deallocators + with a single standard_deallocator, e.g. "delete []". */ + +struct standard_deallocator_set : public deallocator_set +{ + standard_deallocator_set (malloc_state_machine *sm, + const char *name, + enum wording wording); + + bool contains_p (const deallocator *d) const FINAL OVERRIDE; + const deallocator *maybe_get_single () const FINAL OVERRIDE; + void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE; + + standard_deallocator m_deallocator; +}; + +/* Traits class for ensuring uniqueness of deallocator_sets within + malloc_state_machine. */ + +struct deallocator_set_map_traits +{ + typedef custom_deallocator_set::key_t key_type; + typedef custom_deallocator_set *value_type; + typedef custom_deallocator_set *compare_type; + + static inline hashval_t hash (const key_type &k) + { + gcc_assert (k != NULL); + gcc_assert (k != reinterpret_cast<key_type> (1)); + + hashval_t result = 0; + unsigned i; + const deallocator *d; + FOR_EACH_VEC_ELT (*k, i, d) + result ^= d->hash (); + return result; + } + static inline bool equal_keys (const key_type &k1, const key_type &k2) + { + if (k1->length () != k2->length ()) + return false; + + for (unsigned i = 0; i < k1->length (); i++) + if ((*k1)[i] != (*k2)[i]) + return false; + + return true; + } + template <typename T> + static inline void remove (T &) + { + /* empty; the nodes are handled elsewhere. */ + } + template <typename T> + static inline void mark_deleted (T &entry) + { + entry.m_key = reinterpret_cast<key_type> (1); + } + template <typename T> + static inline void mark_empty (T &entry) + { + entry.m_key = NULL; + } + template <typename T> + static inline bool is_deleted (const T &entry) + { + return entry.m_key == reinterpret_cast<key_type> (1); + } + template <typename T> + static inline bool is_empty (const T &entry) + { + return entry.m_key == NULL; + } + static const bool empty_zero_p = false; }; /* A state machine for detecting misuses of the malloc/free API. @@ -167,9 +333,12 @@ public: typedef allocation_state custom_data_t; malloc_state_machine (logger *logger); + ~malloc_state_machine (); state_t - add_state (const char *name, enum resource_state rs, const api *a); + add_state (const char *name, enum resource_state rs, + const deallocator_set *deallocators, + const deallocator *deallocator); bool inherited_state_p () const FINAL OVERRIDE { return false; } @@ -214,9 +383,9 @@ public: bool reset_when_passed_to_unknown_fn_p (state_t s, bool is_mutable) const FINAL OVERRIDE; - api m_malloc; - api m_scalar_new; - api m_vector_new; + standard_deallocator_set m_free; + standard_deallocator_set m_scalar_delete; + standard_deallocator_set m_vector_delete; /* States that are independent of api. */ @@ -232,33 +401,194 @@ public: state_t m_stop; private: + const custom_deallocator_set * + get_or_create_custom_deallocator_set (tree allocator_fndecl); + custom_deallocator_set * + maybe_create_custom_deallocator_set (tree allocator_fndecl); + const deallocator * + get_or_create_deallocator (tree deallocator_fndecl); + void on_allocator_call (sm_context *sm_ctxt, const gcall *call, - const api &ap) const; + const deallocator_set *deallocators, + bool returns_nonnull = false) const; void on_deallocator_call (sm_context *sm_ctxt, const supernode *node, const gcall *call, - const api &ap) const; + const deallocator *d, + unsigned argno) const; void on_zero_assignment (sm_context *sm_ctxt, const gimple *stmt, tree lhs) const; + + /* A map for consolidating deallocators so that they are + unique per deallocator FUNCTION_DECL. */ + typedef hash_map<tree, deallocator *> deallocator_map_t; + deallocator_map_t m_deallocator_map; + + /* Memoized lookups from FUNCTION_DECL to custom_deallocator_set *. */ + typedef hash_map<tree, custom_deallocator_set *> deallocator_set_cache_t; + deallocator_set_cache_t m_custom_deallocator_set_cache; + + /* A map for consolidating custom_deallocator_set instances. */ + typedef hash_map<custom_deallocator_set::key_t, + custom_deallocator_set *, + deallocator_set_map_traits> custom_deallocator_set_map_t; + custom_deallocator_set_map_t m_custom_deallocator_set_map; + + /* Record of dynamically-allocated objects, for cleanup. */ + auto_vec <custom_deallocator_set *> m_dynamic_sets; + auto_vec <custom_deallocator *> m_dynamic_deallocators; }; -/* struct api. */ +/* struct deallocator. */ -api::api (malloc_state_machine *sm, - const char *name, - const char *dealloc_funcname, - enum wording wording) +deallocator::deallocator (malloc_state_machine *sm, + const char *name, + enum wording wording) : m_name (name), - m_dealloc_funcname (dealloc_funcname), m_wording (wording), - m_unchecked (sm->add_state ("unchecked", RS_UNCHECKED, this)), - m_nonnull (sm->add_state ("nonnull", RS_NONNULL, this)), - m_freed (sm->add_state ("freed", RS_FREED, this)) + m_freed (sm->add_state ("freed", RS_FREED, NULL, this)) +{ +} + +hashval_t +deallocator::hash () const +{ + return (hashval_t)m_freed->get_id (); +} + +void +deallocator::dump_to_pp (pretty_printer *pp) const +{ + pp_printf (pp, "%qs", m_name); +} + +int +deallocator::cmp (const deallocator *a, const deallocator *b) +{ + return (int)a->m_freed->get_id () - (int)b->m_freed->get_id (); +} + +int +deallocator::cmp_ptr_ptr (const void *a, const void *b) +{ + return cmp (*(const deallocator * const *)a, + *(const deallocator * const *)b); +} + + +/* struct standard_deallocator : public deallocator. */ + +standard_deallocator::standard_deallocator (malloc_state_machine *sm, + const char *name, + enum wording wording) +: deallocator (sm, name, wording) +{ +} + +/* struct deallocator_set. */ + +deallocator_set::deallocator_set (malloc_state_machine *sm, + enum wording wording) +: m_wording (wording), + m_unchecked (sm->add_state ("unchecked", RS_UNCHECKED, this, NULL)), + m_nonnull (sm->add_state ("nonnull", RS_NONNULL, this, NULL)) +{ +} + +/* Dump a description of this deallocator_set to stderr. */ + +DEBUG_FUNCTION void +deallocator_set::dump () const +{ + pretty_printer pp; + pp_show_color (&pp) = pp_show_color (global_dc->printer); + pp.buffer->stream = stderr; + dump_to_pp (&pp); + pp_newline (&pp); + pp_flush (&pp); +} + +/* struct custom_deallocator_set : public deallocator_set. */ + +custom_deallocator_set:: +custom_deallocator_set (malloc_state_machine *sm, + const auto_vec <const deallocator *> *vec, + enum wording wording) +: deallocator_set (sm, wording), + m_deallocator_vec (vec->length ()) +{ + unsigned i; + const deallocator *d; + FOR_EACH_VEC_ELT (*vec, i, d) + m_deallocator_vec.safe_push (d); +} + +bool +custom_deallocator_set::contains_p (const deallocator *d) const +{ + unsigned i; + const deallocator *cd; + FOR_EACH_VEC_ELT (m_deallocator_vec, i, cd) + if (cd == d) + return true; + return false; +} + +const deallocator * +custom_deallocator_set::maybe_get_single () const +{ + if (m_deallocator_vec.length () == 1) + return m_deallocator_vec[0]; + return NULL; +} + +void +custom_deallocator_set::dump_to_pp (pretty_printer *pp) const +{ + pp_character (pp, '{'); + unsigned i; + const deallocator *d; + FOR_EACH_VEC_ELT (m_deallocator_vec, i, d) + { + if (i > 0) + pp_string (pp, ", "); + d->dump_to_pp (pp); + } + pp_character (pp, '}'); +} + +/* struct standard_deallocator_set : public deallocator_set. */ + +standard_deallocator_set::standard_deallocator_set (malloc_state_machine *sm, + const char *name, + enum wording wording) +: deallocator_set (sm, wording), + m_deallocator (sm, name, wording) { } +bool +standard_deallocator_set::contains_p (const deallocator *d) const +{ + return d == &m_deallocator; +} + +const deallocator * +standard_deallocator_set::maybe_get_single () const +{ + return &m_deallocator; +} + +void +standard_deallocator_set::dump_to_pp (pretty_printer *pp) const +{ + pp_character (pp, '{'); + pp_string (pp, m_deallocator.m_name); + pp_character (pp, '}'); +} + /* Return STATE cast to the custom state subclass, or NULL for the start state. Everything should be an allocation_state apart from the start state. */ @@ -291,6 +621,14 @@ get_rs (state_machine::state_t state) return RS_START; } +/* Return true if STATE is the start state. */ + +static bool +start_p (state_machine::state_t state) +{ + return get_rs (state) == RS_START; +} + /* Return true if STATE is an unchecked result from an allocator. */ static bool @@ -383,10 +721,10 @@ class mismatching_deallocation : public malloc_diagnostic { public: mismatching_deallocation (const malloc_state_machine &sm, tree arg, - const api *expected_dealloc, - const api *actual_dealloc) + const deallocator_set *expected_deallocators, + const deallocator *actual_dealloc) : malloc_diagnostic (sm, arg), - m_expected_dealloc (expected_dealloc), + m_expected_deallocators (expected_deallocators), m_actual_dealloc (actual_dealloc) {} @@ -400,11 +738,18 @@ public: auto_diagnostic_group d; diagnostic_metadata m; m.add_cwe (762); /* CWE-762: Mismatched Memory Management Routines. */ - return warning_meta (rich_loc, m, OPT_Wanalyzer_mismatching_deallocation, - "%qE should have been deallocated with %qs" - " but was deallocated with %qs", - m_arg, m_expected_dealloc->m_dealloc_funcname, - m_actual_dealloc->m_dealloc_funcname); + if (const deallocator *expected_dealloc + = m_expected_deallocators->maybe_get_single ()) + return warning_meta (rich_loc, m, OPT_Wanalyzer_mismatching_deallocation, + "%qE should have been deallocated with %qs" + " but was deallocated with %qs", + m_arg, expected_dealloc->m_name, + m_actual_dealloc->m_name); + else + return warning_meta (rich_loc, m, OPT_Wanalyzer_mismatching_deallocation, + "%qs called on %qE returned from a mismatched" + " allocation function", + m_actual_dealloc->m_name, m_arg); } label_text describe_state_change (const evdesc::state_change &change) @@ -413,9 +758,13 @@ public: if (unchecked_p (change.m_new_state)) { m_alloc_event = change.m_event_id; - return change.formatted_print ("allocated here" - " (expects deallocation with %qs)", - m_expected_dealloc->m_dealloc_funcname); + if (const deallocator *expected_dealloc + = m_expected_deallocators->maybe_get_single ()) + return change.formatted_print ("allocated here" + " (expects deallocation with %qs)", + expected_dealloc->m_name); + else + return change.formatted_print ("allocated here"); } return malloc_diagnostic::describe_state_change (change); } @@ -423,19 +772,28 @@ public: label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE { if (m_alloc_event.known_p ()) - return ev.formatted_print - ("deallocated with %qs here;" - " allocation at %@ expects deallocation with %qs", - m_actual_dealloc->m_dealloc_funcname, &m_alloc_event, - m_expected_dealloc->m_dealloc_funcname); + { + if (const deallocator *expected_dealloc + = m_expected_deallocators->maybe_get_single ()) + return ev.formatted_print + ("deallocated with %qs here;" + " allocation at %@ expects deallocation with %qs", + m_actual_dealloc->m_name, &m_alloc_event, + expected_dealloc->m_name); + else + return ev.formatted_print + ("deallocated with %qs here;" + " allocated at %@", + m_actual_dealloc->m_name, &m_alloc_event); + } return ev.formatted_print ("deallocated with %qs here", - m_actual_dealloc->m_dealloc_funcname); + m_actual_dealloc->m_name); } private: diagnostic_event_id_t m_alloc_event; - const api *m_expected_dealloc; - const api *m_actual_dealloc; + const deallocator_set *m_expected_deallocators; + const deallocator *m_actual_dealloc; }; /* Concrete subclass for reporting double-free diagnostics. */ @@ -455,7 +813,7 @@ public: diagnostic_metadata m; m.add_cwe (415); /* CWE-415: Double Free. */ return warning_meta (rich_loc, m, OPT_Wanalyzer_double_free, - "double-%<%s%> of %qE", m_funcname, m_arg); + "double-%qs of %qE", m_funcname, m_arg); } label_text describe_state_change (const evdesc::state_change &change) @@ -765,9 +1123,12 @@ class use_after_free : public malloc_diagnostic { public: use_after_free (const malloc_state_machine &sm, tree arg, - const api *a) - : malloc_diagnostic (sm, arg), m_api (a) - {} + const deallocator *deallocator) + : malloc_diagnostic (sm, arg), + m_deallocator (deallocator) + { + gcc_assert (deallocator); + } const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; } @@ -778,7 +1139,7 @@ public: m.add_cwe (416); return warning_meta (rich_loc, m, OPT_Wanalyzer_use_after_free, "use after %<%s%> of %qE", - m_api->m_dealloc_funcname, m_arg); + m_deallocator->m_name, m_arg); } label_text describe_state_change (const evdesc::state_change &change) @@ -787,7 +1148,7 @@ public: if (freed_p (change.m_new_state)) { m_free_event = change.m_event_id; - switch (m_api->m_wording) + switch (m_deallocator->m_wording) { default: gcc_unreachable (); @@ -795,6 +1156,8 @@ public: return label_text::borrow ("freed here"); case WORDING_DELETED: return label_text::borrow ("deleted here"); + case WORDING_DEALLOCATED: + return label_text::borrow ("deallocated here"); } } return malloc_diagnostic::describe_state_change (change); @@ -802,9 +1165,9 @@ public: label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE { - const char *funcname = m_api->m_dealloc_funcname; + const char *funcname = m_deallocator->m_name; if (m_free_event.known_p ()) - switch (m_api->m_wording) + switch (m_deallocator->m_wording) { default: gcc_unreachable (); @@ -814,6 +1177,10 @@ public: case WORDING_DELETED: return ev.formatted_print ("use after %<%s%> of %qE; deleted at %@", funcname, ev.m_expr, &m_free_event); + case WORDING_DEALLOCATED: + return ev.formatted_print ("use after %<%s%> of %qE;" + " deallocated at %@", + funcname, ev.m_expr, &m_free_event); } else return ev.formatted_print ("use after %<%s%> of %qE", @@ -822,7 +1189,7 @@ public: private: diagnostic_event_id_t m_free_event; - const api *m_api; + const deallocator *m_deallocator; }; class malloc_leak : public malloc_diagnostic @@ -848,7 +1215,8 @@ public: label_text describe_state_change (const evdesc::state_change &change) FINAL OVERRIDE { - if (unchecked_p (change.m_new_state)) + if (unchecked_p (change.m_new_state) + || (start_p (change.m_old_state) && nonnull_p (change.m_new_state))) { m_alloc_event = change.m_event_id; return label_text::borrow ("allocated here"); @@ -969,40 +1337,161 @@ void allocation_state::dump_to_pp (pretty_printer *pp) const { state_machine::state::dump_to_pp (pp); - if (m_api) - pp_printf (pp, " (%s)", m_api->m_name); + if (m_deallocators) + { + pp_string (pp, " ("); + m_deallocators->dump_to_pp (pp); + pp_character (pp, ')'); + } } -/* Given a allocation_state for an api, get the "nonnull" state - for the corresponding allocator. */ +/* Given a allocation_state for a deallocator_set, get the "nonnull" state + for the corresponding allocator(s). */ const allocation_state * allocation_state::get_nonnull () const { - gcc_assert (m_api); - return as_a_allocation_state (m_api->m_nonnull); + gcc_assert (m_deallocators); + return as_a_allocation_state (m_deallocators->m_nonnull); } /* malloc_state_machine's ctor. */ malloc_state_machine::malloc_state_machine (logger *logger) : state_machine ("malloc", logger), - m_malloc (this, "malloc", "free", WORDING_FREED), - m_scalar_new (this, "new", "delete", WORDING_DELETED), - m_vector_new (this, "new[]", "delete[]", WORDING_DELETED) + m_free (this, "free", WORDING_FREED), + m_scalar_delete (this, "delete", WORDING_DELETED), + m_vector_delete (this, "delete[]", WORDING_DELETED) { gcc_assert (m_start->get_id () == 0); - m_null = add_state ("null", RS_FREED, NULL); - m_non_heap = add_state ("non-heap", RS_NON_HEAP, NULL); - m_stop = add_state ("stop", RS_STOP, NULL); + m_null = add_state ("null", RS_FREED, NULL, NULL); + m_non_heap = add_state ("non-heap", RS_NON_HEAP, NULL, NULL); + m_stop = add_state ("stop", RS_STOP, NULL, NULL); +} + +malloc_state_machine::~malloc_state_machine () +{ + unsigned i; + custom_deallocator_set *set; + FOR_EACH_VEC_ELT (m_dynamic_sets, i, set) + delete set; + custom_deallocator *d; + FOR_EACH_VEC_ELT (m_dynamic_deallocators, i, d) + delete d; } state_machine::state_t malloc_state_machine::add_state (const char *name, enum resource_state rs, - const api *a) + const deallocator_set *deallocators, + const deallocator *deallocator) { return add_custom_state (new allocation_state (name, alloc_state_id (), - rs, a)); + rs, deallocators, + deallocator)); +} + +/* If ALLOCATOR_FNDECL has any "__attribute__((malloc(FOO)))", + return a custom_deallocator_set for them, consolidating them + to ensure uniqueness of the sets. + + Return NULL if it has no such attributes. */ + +const custom_deallocator_set * +malloc_state_machine:: +get_or_create_custom_deallocator_set (tree allocator_fndecl) +{ + /* Early rejection of decls without attributes. */ + tree attrs = DECL_ATTRIBUTES (allocator_fndecl); + if (!attrs) + return NULL; + + /* Otherwise, call maybe_create_custom_deallocator_set, + memoizing the result. */ + if (custom_deallocator_set **slot + = m_custom_deallocator_set_cache.get (allocator_fndecl)) + return *slot; + custom_deallocator_set *set + = maybe_create_custom_deallocator_set (allocator_fndecl); + m_custom_deallocator_set_cache.put (allocator_fndecl, set); + return set; +} + +/* Given ALLOCATOR_FNDECL, a FUNCTION_DECL with attributes, + look for any "__attribute__((malloc(FOO)))" and return a + custom_deallocator_set for them, consolidating them + to ensure uniqueness of the sets. + + Return NULL if it has no such attributes. + + Subroutine of get_or_create_custom_deallocator_set which + memoizes the result. */ + +custom_deallocator_set * +malloc_state_machine:: +maybe_create_custom_deallocator_set (tree allocator_fndecl) +{ + tree attrs = DECL_ATTRIBUTES (allocator_fndecl); + gcc_assert (attrs); + + /* Look for instances of __attribute__((malloc(FOO))). */ + auto_vec<const deallocator *> deallocator_vec; + for (tree allocs = attrs; + (allocs = lookup_attribute ("malloc", allocs)); + allocs = TREE_CHAIN (allocs)) + { + tree args = TREE_VALUE (allocs); + if (!args) + continue; + if (TREE_VALUE (args)) + { + const deallocator *d + = get_or_create_deallocator (TREE_VALUE (args)); + deallocator_vec.safe_push (d); + } + } + + /* If there weren't any deallocators, bail. */ + if (deallocator_vec.length () == 0) + return NULL; + + /* Consolidate, so that we reuse existing deallocator_set + instances. */ + deallocator_vec.qsort (deallocator::cmp_ptr_ptr); + custom_deallocator_set **slot + = m_custom_deallocator_set_map.get (&deallocator_vec); + if (slot) + return *slot; + custom_deallocator_set *set + = new custom_deallocator_set (this, &deallocator_vec, WORDING_DEALLOCATED); + m_custom_deallocator_set_map.put (&set->m_deallocator_vec, set); + m_dynamic_sets.safe_push (set); + return set; +} + +/* Get the deallocator for DEALLOCATOR_FNDECL, creating it if necessary. */ + +const deallocator * +malloc_state_machine::get_or_create_deallocator (tree deallocator_fndecl) +{ + deallocator **slot = m_deallocator_map.get (deallocator_fndecl); + if (slot) + return *slot; + + /* Reuse "free". */ + deallocator *d; + if (is_named_call_p (deallocator_fndecl, "free") + || is_std_named_call_p (deallocator_fndecl, "free")) + d = &m_free.m_deallocator; + else + { + custom_deallocator *cd + = new custom_deallocator (this, deallocator_fndecl, + WORDING_DEALLOCATED); + m_dynamic_deallocators.safe_push (cd); + d = cd; + } + m_deallocator_map.put (deallocator_fndecl, d); + return d; } /* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */ @@ -1024,23 +1513,25 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, || is_named_call_p (callee_fndecl, "strdup", call, 1) || is_named_call_p (callee_fndecl, "strndup", call, 2)) { - on_allocator_call (sm_ctxt, call, m_malloc); + on_allocator_call (sm_ctxt, call, &m_free); return true; } if (is_named_call_p (callee_fndecl, "operator new", call, 1)) - on_allocator_call (sm_ctxt, call, m_scalar_new); + on_allocator_call (sm_ctxt, call, &m_scalar_delete); else if (is_named_call_p (callee_fndecl, "operator new []", call, 1)) - on_allocator_call (sm_ctxt, call, m_vector_new); + on_allocator_call (sm_ctxt, call, &m_vector_delete); else if (is_named_call_p (callee_fndecl, "operator delete", call, 1) || is_named_call_p (callee_fndecl, "operator delete", call, 2)) { - on_deallocator_call (sm_ctxt, node, call, m_scalar_new); + on_deallocator_call (sm_ctxt, node, call, + &m_scalar_delete.m_deallocator, 0); return true; } else if (is_named_call_p (callee_fndecl, "operator delete []", call, 1)) { - on_deallocator_call (sm_ctxt, node, call, m_vector_new); + on_deallocator_call (sm_ctxt, node, call, + &m_vector_delete.m_deallocator, 0); return true; } @@ -1057,10 +1548,26 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, || is_std_named_call_p (callee_fndecl, "free", call, 1) || is_named_call_p (callee_fndecl, "__builtin_free", call, 1)) { - on_deallocator_call (sm_ctxt, node, call, m_malloc); + on_deallocator_call (sm_ctxt, node, call, + &m_free.m_deallocator, 0); return true; } + /* Cast away const-ness for cache-like operations. */ + malloc_state_machine *mutable_this + = const_cast <malloc_state_machine *> (this); + + /* Handle "__attribute__((malloc(FOO)))". */ + if (const deallocator_set *deallocators + = mutable_this->get_or_create_custom_deallocator_set + (callee_fndecl)) + { + tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl)); + bool returns_nonnull + = lookup_attribute ("returns_nonnull", attrs); + on_allocator_call (sm_ctxt, call, deallocators, returns_nonnull); + } + /* Handle "__attribute__((nonnull))". */ { tree fntype = TREE_TYPE (callee_fndecl); @@ -1103,6 +1610,16 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, BITMAP_FREE (nonnull_args); } } + + /* Check for this after nonnull, so that if we have both + then we transition to "freed", rather than "checked". */ + unsigned dealloc_argno = fndecl_dealloc_argno (callee_fndecl); + if (dealloc_argno != UINT_MAX) + { + const deallocator *d + = mutable_this->get_or_create_deallocator (callee_fndecl); + on_deallocator_call (sm_ctxt, node, call, d, dealloc_argno); + } } if (tree lhs = sm_ctxt->is_zero_assignment (stmt)) @@ -1162,7 +1679,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, const allocation_state *astate = as_a_allocation_state (state); sm_ctxt->warn (node, stmt, arg, new use_after_free (*this, diag_arg, - astate->m_api)); + astate->m_deallocator)); sm_ctxt->set_next_state (stmt, arg, m_stop); } } @@ -1170,18 +1687,24 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, return false; } -/* Handle a call to an allocator. */ +/* Handle a call to an allocator. + RETURNS_NONNULL is true if CALL is to a fndecl known to have + __attribute__((returns_nonnull)). */ void malloc_state_machine::on_allocator_call (sm_context *sm_ctxt, const gcall *call, - const api &ap) const + const deallocator_set *deallocators, + bool returns_nonnull) const { tree lhs = gimple_call_lhs (call); if (lhs) { if (sm_ctxt->get_state (call, lhs) == m_start) - sm_ctxt->set_next_state (call, lhs, ap.m_unchecked); + sm_ctxt->set_next_state (call, lhs, + (returns_nonnull + ? deallocators->m_nonnull + : deallocators->m_unchecked)); } else { @@ -1193,40 +1716,42 @@ void malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, const supernode *node, const gcall *call, - const api &ap) const + const deallocator *d, + unsigned argno) const { - tree arg = gimple_call_arg (call, 0); + if (argno >= gimple_call_num_args (call)) + return; + tree arg = gimple_call_arg (call, argno); tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); state_t state = sm_ctxt->get_state (call, arg); /* start/unchecked/nonnull -> freed. */ if (state == m_start) - sm_ctxt->set_next_state (call, arg, ap.m_freed); + sm_ctxt->set_next_state (call, arg, d->m_freed); else if (unchecked_p (state) || nonnull_p (state)) { const allocation_state *astate = as_a_allocation_state (state); - - if (astate->m_api != &ap) + gcc_assert (astate->m_deallocators); + if (!astate->m_deallocators->contains_p (d)) { /* Wrong allocator. */ - pending_diagnostic *d + pending_diagnostic *pd = new mismatching_deallocation (*this, diag_arg, - astate->m_api, &ap); - sm_ctxt->warn (node, call, arg, d); + astate->m_deallocators, + d); + sm_ctxt->warn (node, call, arg, pd); } - sm_ctxt->set_next_state (call, arg, ap.m_freed); + sm_ctxt->set_next_state (call, arg, d->m_freed); } /* Keep state "null" as-is, rather than transitioning to "freed"; we don't want to complain about double-free of NULL. */ - - else if (state == ap.m_freed) + else if (state == d->m_freed) { /* freed -> stop, with warning. */ sm_ctxt->warn (node, call, arg, - new double_free (*this, diag_arg, - ap.m_dealloc_funcname)); + new double_free (*this, diag_arg, d->m_name)); sm_ctxt->set_next_state (call, arg, m_stop); } else if (state == m_non_heap) @@ -1234,7 +1759,7 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, /* non-heap -> stop, with warning. */ sm_ctxt->warn (node, call, arg, new free_of_non_heap (*this, diag_arg, - ap.m_dealloc_funcname)); + d->m_name)); sm_ctxt->set_next_state (call, arg, m_stop); } } diff --git a/gcc/attribs.h b/gcc/attribs.h index 9e3e56a..21d28a4 100644 --- a/gcc/attribs.h +++ b/gcc/attribs.h @@ -310,4 +310,6 @@ extern void init_attr_rdwr_indices (rdwr_map *, tree); extern attr_access *get_parm_access (rdwr_map &, tree, tree = current_function_decl); +extern unsigned fndecl_dealloc_argno (tree fndecl); + #endif // GCC_ATTRIBS_H diff --git a/gcc/builtins.c b/gcc/builtins.c index 02e7815..c1115a3 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -13014,6 +13014,16 @@ call_dealloc_argno (tree exp) if (!fndecl) return UINT_MAX; + return fndecl_dealloc_argno (fndecl); +} + +/* Return the zero-based number corresponding to the argument being + deallocated if FNDECL is a deallocation function or UINT_MAX + if it isn't. */ + +unsigned +fndecl_dealloc_argno (tree fndecl) +{ /* A call to operator delete isn't recognized as one to a built-in. */ if (DECL_IS_OPERATOR_DELETE_P (fndecl)) return 0; diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index c5b1faf..8daa1c6 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -3291,6 +3291,58 @@ __attribute__ ((malloc, malloc (fclose (1)))) FILE* tmpfile (void); @end smallexample +The warnings guarded by @option{-fanalyzer} respect allocation and +deallocation pairs marked with the @code{malloc}. In particular: + +@itemize @bullet + +@item +The analyzer will emit a @option{-Wanalyzer-mismatching-deallocation} +diagnostic if there is an execution path in which the result of an +allocation call is passed to a different deallocator. + +@item +The analyzer will emit a @option{-Wanalyzer-double-free} +diagnostic if there is an execution path in which a value is passed +more than once to a deallocation call. + +@item +The analyzer will consider the possibility that an allocation function +could fail and return NULL. It will emit +@option{-Wanalyzer-possible-null-dereference} and +@option{-Wanalyzer-possible-null-argument} diagnostics if there are +execution paths in which an unchecked result of an allocation call is +dereferenced or passed to a function requiring a non-null argument. +If the allocator always returns non-null, use +@code{__attribute__ ((returns_nonnull))} to suppress these warnings. +For example: +@smallexample +char *xstrdup (const char *) + __attribute__((malloc (free), returns_nonnull)); +@end smallexample + +@item +The analyzer will emit a @option{-Wanalyzer-use-after-free} +diagnostic if there is an execution path in which the memory passed +by pointer to a deallocation call is used after the deallocation. + +@item +The analyzer will emit a @option{-Wanalyzer-malloc-leak} diagnostic if +there is an execution path in which the result of an allocation call +is leaked (without being passed to the deallocation function). + +@item +The analyzer will emit a @option{-Wanalyzer-free-of-non-heap} diagnostic +if a deallocation function is used on a global or on-stack variable. + +@end itemize + +The analyzer assumes that deallocators can gracefully handle the @code{NULL} +pointer. If this is not the case, the deallocator can be marked with +@code{__attribute__((nonnull))} so that @option{-fanalyzer} can emit +a @option{-Wanalyzer-possible-null-argument} diagnostic for code paths +in which the deallocator is called with NULL. + @item no_icf @cindex @code{no_icf} function attribute This function attribute prevents a functions from being merged with another diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index c290b6f..5077ea7 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -9155,7 +9155,8 @@ This warning requires @option{-fanalyzer}, which enables it; use @option{-Wno-analyzer-double-free} to disable it. This diagnostic warns for paths through the code in which a pointer -can have @code{free} called on it more than once. +can have a deallocator called on it more than once, either @code{free}, +or a deallocator referenced by attribute @code{malloc}. @item -Wno-analyzer-exposure-through-output-file @opindex Wanalyzer-exposure-through-output-file @@ -9196,7 +9197,8 @@ This warning requires @option{-fanalyzer}, which enables it; use to disable it. This diagnostic warns for paths through the code in which a -pointer allocated via @code{malloc} is leaked. +pointer allocated via an allocator is leaked: either @code{malloc}, +or a function marked with attribute @code{malloc}. @item -Wno-analyzer-mismatching-deallocation @opindex Wanalyzer-mismatching-deallocation @@ -9207,7 +9209,10 @@ to disable it. This diagnostic warns for paths through the code in which the wrong deallocation function is called on a pointer value, based on -which function was used to allocate the pointer value. +which function was used to allocate the pointer value. The diagnostic +will warn about mismatches between @code{free}, scalar @code{delete} +and vector @code{delete[]}, and those marked as allocator/deallocator +pairs using attribute @code{malloc}. @item -Wno-analyzer-possible-null-argument @opindex Wanalyzer-possible-null-argument @@ -9322,7 +9327,8 @@ This warning requires @option{-fanalyzer}, which enables it; use @option{-Wno-analyzer-use-after-free} to disable it. This diagnostic warns for paths through the code in which a -pointer is used after @code{free} is called on it. +pointer is used after a deallocator is called on it: either @code{free}, +or a deallocator referenced by attribute @code{malloc}. @item -Wno-analyzer-use-of-pointer-in-stale-stack-frame @opindex Wanalyzer-use-of-pointer-in-stale-stack-frame 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'" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-2.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-2.c new file mode 100644 index 0000000..09d941f --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-2.c @@ -0,0 +1,24 @@ +extern void free (void *); +char *xstrdup (const char *) + __attribute__((malloc (free), returns_nonnull)); + +void test_1 (const char *s) +{ + char *p = xstrdup (s); + free (p); +} + +/* Verify that we don't issue -Wanalyzer-possible-null-dereference + when the allocator has __attribute__((returns_nonnull)). */ + +char *test_2 (const char *s) +{ + char *p = xstrdup (s); + p[0] = 'a'; /* { dg-bogus "possibly-NULL" } */ + return p; +} + +void test_3 (const char *s) +{ + char *p = xstrdup (s); /* { dg-message "allocated here" } */ +} /* { dg-warning "leak of 'p'" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-4.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-4.c new file mode 100644 index 0000000..1517667 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-4.c @@ -0,0 +1,21 @@ +/* An example where the deallocator requires non-NULL. */ + +struct foo; +extern void foo_release (struct foo *) + __attribute__((nonnull)); +extern struct foo *foo_acquire (void) + __attribute__ ((malloc (foo_release))); + +void test_1 (void) +{ + struct foo *p = foo_acquire (); /* { dg-message "this call could return NULL" } */ + foo_release (p); /* { dg-warning "use of possibly-NULL 'p' where non-null" } */ +} + +void test_2 (void) +{ + struct foo *p = foo_acquire (); + if (!p) + return; + foo_release (p); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-5.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-5.c new file mode 100644 index 0000000..7ff4e57 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-5.c @@ -0,0 +1,12 @@ +/* Example of extra argument to "malloc" attribute. */ + +struct foo; +extern void foo_release (int, struct foo *); +extern struct foo *foo_acquire (void) + __attribute__ ((malloc (foo_release, 2))); + +void test_1 (void) +{ + struct foo *p = foo_acquire (); + foo_release (0, p); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c new file mode 100644 index 0000000..bd28107 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c @@ -0,0 +1,228 @@ +/* Adapted from gcc.dg/Wmismatched-dealloc.c. */ + +#define A(...) __attribute__ ((malloc (__VA_ARGS__))) + +typedef struct FILE FILE; +typedef __SIZE_TYPE__ size_t; + +void free (void*); +void* malloc (size_t); +void* realloc (void*, size_t); + +int fclose (FILE*); +FILE* freopen (const char*, const char*, FILE*); +int pclose (FILE*); + +A (fclose) A (freopen, 3) + FILE* fdopen (int); +A (fclose) A (freopen, 3) + FILE* fopen (const char*, const char*); +A (fclose) A (freopen, 3) + FILE* fmemopen(void *, size_t, const char *); +A (fclose) A (freopen, 3) + FILE* freopen (const char*, const char*, FILE*); +A (pclose) A (freopen, 3) + FILE* popen (const char*, const char*); +A (fclose) A (freopen, 3) + FILE* tmpfile (void); + +void sink (FILE*); + + + void release (void*); +A (release) FILE* acquire (void); + +void nowarn_fdopen (void) +{ + { + FILE *q = fdopen (0); + if (!q) + return; + + fclose (q); + } + + { + FILE *q = fdopen (0); + if (!q) + return; + + q = freopen ("1", "r", q); + fclose (q); + } + + { + FILE *q = fdopen (0); + if (!q) + return; + + sink (q); + } +} + + +void warn_fdopen (void) +{ + { + FILE *q = fdopen (0); // { dg-message "allocated here" } + release (q); // { dg-warning "'release' called on 'q' returned from a mismatched allocation function" } + } + { + FILE *q = fdopen (0); // { dg-message "allocated here" } + free (q); // { dg-warning "'free' called on 'q' returned from a mismatched allocation function" } + } + + { + FILE *q = fdopen (0); // { dg-message "allocated here" } + q = realloc (q, 7); // { dg-warning "'realloc' called on 'q' returned from a mismatched allocation function" } + sink (q); + } +} + + +void nowarn_fopen (void) +{ + { + FILE *q = fopen ("1", "r"); + sink (q); + fclose (q); + } + + { + FILE *q = fopen ("2", "r"); + sink (q); + q = freopen ("3", "r", q); + sink (q); + fclose (q); + } + + { + FILE *q = fopen ("4", "r"); + sink (q); + } +} + + +void warn_fopen (void) +{ + { + FILE *q = fopen ("1", "r"); + release (q); // { dg-warning "'release' called on 'q' returned from a mismatched allocation function" } + fclose (q); + } + { + FILE *q = fdopen (0); + free (q); // { dg-warning "'free' called on 'q' returned from a mismatched allocation function" } + } + + { + FILE *q = fdopen (0); + q = realloc (q, 7); // { dg-warning "'realloc' called on 'q' returned from a mismatched allocation function" } + sink (q); + } +} + + +void test_popen (void) +{ + { + FILE *p = popen ("1", "r"); + sink (p); + pclose (p); + } + + { + FILE *p; + p = popen ("2", "r"); // { dg-message "allocated here" } + fclose (p); // { dg-warning "'fclose' called on 'p' returned from a mismatched allocation function" } + } + + { + /* freopen() can close a stream open by popen() but pclose() can't + close the stream returned from freopen(). */ + FILE *p = popen ("2", "r"); + p = freopen ("3", "r", p); // { dg-message "allocated here" } + pclose (p); // { dg-warning "'pclose' called on 'p' returned from a mismatched allocation function" } + } +} + + +void test_tmpfile (void) +{ + { + FILE *p = tmpfile (); + fclose (p); + } + + { + FILE *p = tmpfile (); + p = freopen ("1", "r", p); + fclose (p); + } + + { + FILE *p = tmpfile (); // { dg-message "allocated here" } + pclose (p); // { dg-warning "'pclose' called on 'p' returned from a mismatched allocation function" } + } +} + + +void warn_malloc (void) +{ + { + FILE *p = malloc (100); // { dg-message "allocated here" } + fclose (p); // { dg-warning "'p' should have been deallocated with 'free' but was deallocated with 'fclose'" } + } + + { + FILE *p = malloc (100); // { dg-message "allocated here" } + p = freopen ("1", "r", p);// { dg-warning "'p' should have been deallocated with 'free' but was deallocated with 'freopen'" } + fclose (p); + } + + { + FILE *p = malloc (100); // { dg-message "allocated here" } + pclose (p); // { dg-warning "'p' should have been deallocated with 'free' but was deallocated with 'pclose'" } + } +} + + +void test_acquire (void) +{ + { + FILE *p = acquire (); + release (p); + } + + { + FILE *p = acquire (); + release (p); + } + + { + FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" } + fclose (p); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'fclose'" } + } + + { + FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" } + pclose (p); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'pclose'" } + } + + { + FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" } + p = freopen ("1", "r", p); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'freopen'" } + sink (p); + } + + { + FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" } + free (p); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'free'" } + } + + { + FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" } + p = realloc (p, 123); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'realloc'" } + sink (p); + } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c new file mode 100644 index 0000000..905d50e --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c @@ -0,0 +1,224 @@ +/* Adapted from linux 5.3.11: drivers/net/wireless/ath/ath10k/usb.c + Reduced reproducer for CVE-2019-19078 (leak of struct urb). */ + +typedef unsigned char u8; +typedef unsigned short u16; +typedef _Bool bool; + +#define ENOMEM 12 +#define EINVAL 22 + +/* The original file has this licence header. */ + +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2007-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012,2017 Qualcomm Atheros, Inc. + * Copyright (c) 2016-2017 Erik Stromdahl <erik.stromdahl@gmail.com> + */ + +/* Adapted from include/linux/compiler_attributes.h. */ +#define __aligned(x) __attribute__((__aligned__(x))) +#define __printf(a, b) __attribute__((__format__(printf, a, b))) + +/* Possible macro for the new attribute. */ +#define __malloc(f) __attribute__((malloc(f))); + +/* From include/linux/types.h. */ + +typedef unsigned int gfp_t; + +/* Not the real value, which is in include/linux/gfp.h. */ +#define GFP_ATOMIC 32 + +/* From include/linux/usb.h. */ + +struct urb; +extern void usb_free_urb(struct urb *urb); +extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags) + __malloc(usb_free_urb); +/* attribute added as part of testcase */ + +extern int usb_submit_urb(/*struct urb *urb, */gfp_t mem_flags); +extern void usb_unanchor_urb(struct urb *urb); + +/* From drivers/net/wireless/ath/ath10k/core.h. */ + +struct ath10k; + +struct ath10k { + /* [...many other fields removed...] */ + + /* must be last */ + u8 drv_priv[0] __aligned(sizeof(void *)); +}; + +/* From drivers/net/wireless/ath/ath10k/debug.h. */ + +enum ath10k_debug_mask { + /* [...other values removed...] */ + ATH10K_DBG_USB_BULK = 0x00080000, +}; + +extern unsigned int ath10k_debug_mask; + +__printf(3, 4) void __ath10k_dbg(struct ath10k *ar, + enum ath10k_debug_mask mask, + const char *fmt, ...); + +/* Simplified for now, to avoid pulling in tracepoint code. */ +static inline +bool trace_ath10k_log_dbg_enabled(void) { return 0; } + +#define ath10k_dbg(ar, dbg_mask, fmt, ...) \ +do { \ + if ((ath10k_debug_mask & dbg_mask) || \ + trace_ath10k_log_dbg_enabled()) \ + __ath10k_dbg(ar, dbg_mask, fmt, ##__VA_ARGS__); \ +} while (0) + +/* From drivers/net/wireless/ath/ath10k/hif.h. */ + +struct ath10k_hif_sg_item { + /* [...other fields removed...] */ + void *transfer_context; /* NULL = tx completion callback not called */ +}; + +struct ath10k_hif_ops { + /* send a scatter-gather list to the target */ + int (*tx_sg)(struct ath10k *ar, u8 pipe_id, + struct ath10k_hif_sg_item *items, int n_items); + /* [...other fields removed...] */ +}; + +/* From drivers/net/wireless/ath/ath10k/usb.h. */ + +/* tx/rx pipes for usb */ +enum ath10k_usb_pipe_id { + /* [...other values removed...] */ + ATH10K_USB_PIPE_MAX = 8 +}; + +struct ath10k_usb_pipe { + /* [...all fields removed...] */ +}; + +/* usb device object */ +struct ath10k_usb { + /* [...other fields removed...] */ + struct ath10k_usb_pipe pipes[ATH10K_USB_PIPE_MAX]; +}; + +/* usb urb object */ +struct ath10k_urb_context { + /* [...other fields removed...] */ + struct ath10k_usb_pipe *pipe; + struct sk_buff *skb; +}; + +static inline struct ath10k_usb *ath10k_usb_priv(struct ath10k *ar) +{ + return (struct ath10k_usb *)ar->drv_priv; +} + +/* The source file. */ + +static void ath10k_usb_post_recv_transfers(struct ath10k *ar, + struct ath10k_usb_pipe *recv_pipe); + +struct ath10k_urb_context * +ath10k_usb_alloc_urb_from_pipe(struct ath10k_usb_pipe *pipe); + +void ath10k_usb_free_urb_to_pipe(struct ath10k_usb_pipe *pipe, + struct ath10k_urb_context *urb_context); + +static int ath10k_usb_hif_tx_sg(struct ath10k *ar, u8 pipe_id, + struct ath10k_hif_sg_item *items, int n_items) +{ + struct ath10k_usb *ar_usb = ath10k_usb_priv(ar); + struct ath10k_usb_pipe *pipe = &ar_usb->pipes[pipe_id]; + struct ath10k_urb_context *urb_context; + struct sk_buff *skb; + struct urb *urb; + int ret, i; + + for (i = 0; i < n_items; i++) { + urb_context = ath10k_usb_alloc_urb_from_pipe(pipe); + if (!urb_context) { + ret = -ENOMEM; + goto err; + } + + skb = items[i].transfer_context; + urb_context->skb = skb; + + urb = usb_alloc_urb(0, GFP_ATOMIC); /* { dg-message "allocated here" } */ + if (!urb) { + ret = -ENOMEM; + goto err_free_urb_to_pipe; + } + + /* TODO: these are disabled, otherwise we conservatively + assume that they could free urb. */ +#if 0 + usb_fill_bulk_urb(urb, + ar_usb->udev, + pipe->usb_pipe_handle, + skb->data, + skb->len, + ath10k_usb_transmit_complete, urb_context); + if (!(skb->len % pipe->max_packet_size)) { + /* hit a max packet boundary on this pipe */ + urb->transfer_flags |= URB_ZERO_PACKET; + } + + usb_anchor_urb(urb, &pipe->urb_submitted); +#endif + /* TODO: initial argument disabled, otherwise we conservatively + assume that it could free urb. */ + ret = usb_submit_urb(/*urb, */GFP_ATOMIC); + if (ret) { /* TODO: why doesn't it show this condition at default verbosity? */ + ath10k_dbg(ar, ATH10K_DBG_USB_BULK, + "usb bulk transmit failed: %d\n", ret); + + /* TODO: this is disabled, otherwise we conservatively + assume that it could free urb. */ +#if 0 + usb_unanchor_urb(urb); +#endif + + ret = -EINVAL; + /* Leak of urb happens here. */ + goto err_free_urb_to_pipe; + } + + /* TODO: the loop confuses the double-free checker (another + instance of PR analyzer/93695). */ + usb_free_urb(urb); /* { dg-bogus "double-'usb_free_urb' of 'urb'" "" { xfail *-*-* } } */ + } + + return 0; + +err_free_urb_to_pipe: + ath10k_usb_free_urb_to_pipe(urb_context->pipe, urb_context); +err: + return ret; /* { dg-warning "leak of 'urb'" } */ +} + +static const struct ath10k_hif_ops ath10k_usb_hif_ops = { + .tx_sg = ath10k_usb_hif_tx_sg, +}; + +/* Simulate code to register the callback. */ +extern void callback_registration (const void *); +int ath10k_usb_probe(void) +{ + callback_registration(&ath10k_usb_hif_ops); +} + + +/* The original source file ends with: +MODULE_AUTHOR("Atheros Communications, Inc."); +MODULE_DESCRIPTION("Driver support for Qualcomm Atheros 802.11ac WLAN USB devices"); +MODULE_LICENSE("Dual BSD/GPL"); +*/ diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-misuses.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-misuses.c new file mode 100644 index 0000000..3c6c17b --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-misuses.c @@ -0,0 +1,18 @@ +extern void free (void *); + +int not_a_fn __attribute__ ((malloc (free))); /* { dg-warning "'malloc' attribute ignored; valid only for functions" } */ + +void void_return (void) __attribute__ ((malloc(free))); /* { dg-warning "'malloc' attribute ignored on functions returning 'void'" } */ + +void test_void_return (void) +{ + void_return (); +} + +extern void void_args (void); /* { dg-message "declared here" } */ +void *has_malloc_with_void_args (void) + __attribute__ ((malloc(void_args))); /* { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'void'" } */ + +extern void no_args (); /* { dg-message "declared here" } */ +void *has_malloc_with_no_args (void) + __attribute__ ((malloc(no_args))); /* { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument" } */ |