diff options
author | David Malcolm <dmalcolm@redhat.com> | 2021-11-12 10:06:23 -0500 |
---|---|---|
committer | David Malcolm <dmalcolm@redhat.com> | 2022-01-13 20:18:20 -0500 |
commit | b31cec9c22b8dfa40baefd4c2dd774477e8e04c5 (patch) | |
tree | 4675de9d964069a4cf3d2b6c630e440bb8e022ff /gcc/analyzer/engine.cc | |
parent | ad3f0d0806d64ae8ceddfbde1560d4829085202b (diff) |
Add __attribute__ ((tainted_args))
This patch adds a new __attribute__ ((tainted_args)) to the C/C++ frontends.
It can be used on function decls: the analyzer will treat as tainted
all parameters to the function and all buffers pointed to by parameters
to the function. Adding this in one place to the Linux kernel's
__SYSCALL_DEFINEx macro allows the analyzer to treat all syscalls as
having tainted inputs. This gives some coverage of system calls without
needing to "teach" the analyzer about "__user" - an example of the use
of this can be seen in CVE-2011-2210, where given:
SYSCALL_DEFINE5(osf_getsysinfo, unsigned long, op, void __user *, buffer,
unsigned long, nbytes, int __user *, start, void __user *, arg)
the analyzer will treat the nbytes param as under attacker control, and
can complain accordingly:
taint-CVE-2011-2210-1.c: In function 'sys_osf_getsysinfo':
taint-CVE-2011-2210-1.c:69:21: warning: use of attacker-controlled value
'nbytes' as size without upper-bounds checking [CWE-129] [-Wanalyzer-tainted-size]
69 | if (copy_to_user(buffer, hwrpb, nbytes) != 0)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Additionally, the patch allows the attribute to be used on field decls:
specifically function pointers. Any function used as an initializer
for such a field gets treated as being called with tainted arguments.
An example can be seen in CVE-2020-13143, where adding
__attribute__((tainted_args)) to the "store" callback of
configfs_attribute:
struct configfs_attribute {
/* [...snip...] */
ssize_t (*store)(struct config_item *, const char *, size_t)
__attribute__((tainted_args));
/* [...snip...] */
};
allows the analyzer to see:
CONFIGFS_ATTR(gadget_dev_desc_, UDC);
and treat gadget_dev_desc_UDC_store as having tainted arguments, so that
it complains:
taint-CVE-2020-13143-1.c: In function 'gadget_dev_desc_UDC_store':
taint-CVE-2020-13143-1.c:33:17: warning: use of attacker-controlled value
'len + 18446744073709551615' as offset without upper-bounds checking [CWE-823] [-Wanalyzer-tainted-offset]
33 | if (name[len - 1] == '\n')
| ~~~~^~~~~~~~~
As before this currently still needs -fanalyzer-checker=taint (in
addition to -fanalyzer).
gcc/analyzer/ChangeLog:
* engine.cc: Include "stringpool.h", "attribs.h", and
"tree-dfa.h".
(mark_params_as_tainted): New.
(class tainted_args_function_custom_event): New.
(class tainted_args_function_info): New.
(exploded_graph::add_function_entry): Handle functions with
"tainted_args" attribute.
(class tainted_args_field_custom_event): New.
(class tainted_args_callback_custom_event): New.
(class tainted_args_call_info): New.
(add_tainted_args_callback): New.
(add_any_callbacks): New.
(exploded_graph::build_initial_worklist): Likewise.
(exploded_graph::build_initial_worklist): Find callbacks that are
reachable from global initializers, calling add_any_callbacks on
them.
gcc/c-family/ChangeLog:
* c-attribs.c (c_common_attribute_table): Add "tainted_args".
(handle_tainted_args_attribute): New.
gcc/ChangeLog:
* doc/extend.texi (Function Attributes): Note that "tainted_args" can
be used on field decls.
(Common Function Attributes): Add entry on "tainted_args" attribute.
gcc/testsuite/ChangeLog:
* gcc.dg/analyzer/attr-tainted_args-1.c: New test.
* gcc.dg/analyzer/attr-tainted_args-misuses.c: New test.
* gcc.dg/analyzer/taint-CVE-2011-2210-1.c: New test.
* gcc.dg/analyzer/taint-CVE-2020-13143-1.c: New test.
* gcc.dg/analyzer/taint-CVE-2020-13143-2.c: New test.
* gcc.dg/analyzer/taint-CVE-2020-13143.h: New test.
* gcc.dg/analyzer/taint-alloc-3.c: New test.
* gcc.dg/analyzer/taint-alloc-4.c: New test.
* gcc.dg/analyzer/test-uaccess.h: New test.
Signed-off-by: David Malcolm <dmalcolm@redhat.com>
Diffstat (limited to 'gcc/analyzer/engine.cc')
-rw-r--r-- | gcc/analyzer/engine.cc | 320 |
1 files changed, 318 insertions, 2 deletions
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 8b6f4c83f0f..243235e4cd4 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -68,6 +68,9 @@ along with GCC; see the file COPYING3. If not see #include "plugin.h" #include "target.h" #include <memory> +#include "stringpool.h" +#include "attribs.h" +#include "tree-dfa.h" /* For an overview, see gcc/doc/analyzer.texi. */ @@ -2287,6 +2290,116 @@ exploded_graph::~exploded_graph () delete (*iter).second; } +/* Subroutine for use when implementing __attribute__((tainted_args)) + on functions and on function pointer fields in structs. + + Called on STATE representing a call to FNDECL. + Mark all params of FNDECL in STATE as "tainted". Mark the value of all + regions pointed to by params of FNDECL as "tainted". + + Return true if successful; return false if the "taint" state machine + was not found. */ + +static bool +mark_params_as_tainted (program_state *state, tree fndecl, + const extrinsic_state &ext_state) +{ + unsigned taint_sm_idx; + if (!ext_state.get_sm_idx_by_name ("taint", &taint_sm_idx)) + return false; + sm_state_map *smap = state->m_checker_states[taint_sm_idx]; + + const state_machine &sm = ext_state.get_sm (taint_sm_idx); + state_machine::state_t tainted = sm.get_state_by_name ("tainted"); + + region_model_manager *mgr = ext_state.get_model_manager (); + + function *fun = DECL_STRUCT_FUNCTION (fndecl); + gcc_assert (fun); + + for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm; + iter_parm = DECL_CHAIN (iter_parm)) + { + tree param = iter_parm; + if (tree parm_default_ssa = ssa_default_def (fun, iter_parm)) + param = parm_default_ssa; + const region *param_reg = state->m_region_model->get_lvalue (param, NULL); + const svalue *init_sval = mgr->get_or_create_initial_value (param_reg); + smap->set_state (state->m_region_model, init_sval, + tainted, NULL /*origin_new_sval*/, ext_state); + if (POINTER_TYPE_P (TREE_TYPE (param))) + { + const region *pointee_reg = mgr->get_symbolic_region (init_sval); + /* Mark "*param" as tainted. */ + const svalue *init_pointee_sval + = mgr->get_or_create_initial_value (pointee_reg); + smap->set_state (state->m_region_model, init_pointee_sval, + tainted, NULL /*origin_new_sval*/, ext_state); + } + } + + return true; +} + +/* Custom event for use by tainted_args_function_info when a function + has been marked with __attribute__((tainted_args)). */ + +class tainted_args_function_custom_event : public custom_event +{ +public: + tainted_args_function_custom_event (location_t loc, tree fndecl, int depth) + : custom_event (loc, fndecl, depth), + m_fndecl (fndecl) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE + { + return make_label_text + (can_colorize, + "function %qE marked with %<__attribute__((tainted_args))%>", + m_fndecl); + } + +private: + tree m_fndecl; +}; + +/* Custom exploded_edge info for top-level calls to a function + marked with __attribute__((tainted_args)). */ + +class tainted_args_function_info : public custom_edge_info +{ +public: + tainted_args_function_info (tree fndecl) + : m_fndecl (fndecl) + {} + + void print (pretty_printer *pp) const FINAL OVERRIDE + { + pp_string (pp, "call to tainted_args function"); + }; + + bool update_model (region_model *, + const exploded_edge *, + region_model_context *) const FINAL OVERRIDE + { + /* No-op. */ + return true; + } + + void add_events_to_path (checker_path *emission_path, + const exploded_edge &) const FINAL OVERRIDE + { + emission_path->add_event + (new tainted_args_function_custom_event + (DECL_SOURCE_LOCATION (m_fndecl), m_fndecl, 0)); + } + +private: + tree m_fndecl; +}; + /* Ensure that there is an exploded_node representing an external call to FUN, adding it to the worklist if creating it. @@ -2313,14 +2426,25 @@ exploded_graph::add_function_entry (function *fun) program_state state (m_ext_state); state.push_frame (m_ext_state, fun); + custom_edge_info *edge_info = NULL; + + if (lookup_attribute ("tainted_args", DECL_ATTRIBUTES (fun->decl))) + { + if (mark_params_as_tainted (&state, fun->decl, m_ext_state)) + edge_info = new tainted_args_function_info (fun->decl); + } + if (!state.m_valid) return NULL; exploded_node *enode = get_or_create_node (point, state, NULL); if (!enode) - return NULL; + { + delete edge_info; + return NULL; + } - add_edge (m_origin, enode, NULL); + add_edge (m_origin, enode, NULL, edge_info); m_functions_with_enodes.add (fun); @@ -2634,6 +2758,187 @@ toplevel_function_p (function *fun, logger *logger) return true; } +/* Custom event for use by tainted_call_info when a callback field has been + marked with __attribute__((tainted_args)), for labelling the field. */ + +class tainted_args_field_custom_event : public custom_event +{ +public: + tainted_args_field_custom_event (tree field) + : custom_event (DECL_SOURCE_LOCATION (field), NULL_TREE, 0), + m_field (field) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE + { + return make_label_text (can_colorize, + "field %qE of %qT" + " is marked with %<__attribute__((tainted_args))%>", + m_field, DECL_CONTEXT (m_field)); + } + +private: + tree m_field; +}; + +/* Custom event for use by tainted_call_info when a callback field has been + marked with __attribute__((tainted_args)), for labelling the function used + in that callback. */ + +class tainted_args_callback_custom_event : public custom_event +{ +public: + tainted_args_callback_custom_event (location_t loc, tree fndecl, int depth, + tree field) + : custom_event (loc, fndecl, depth), + m_field (field) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE + { + return make_label_text (can_colorize, + "function %qE used as initializer for field %qE" + " marked with %<__attribute__((tainted_args))%>", + m_fndecl, m_field); + } + +private: + tree m_field; +}; + +/* Custom edge info for use when adding a function used by a callback field + marked with '__attribute__((tainted_args))'. */ + +class tainted_args_call_info : public custom_edge_info +{ +public: + tainted_args_call_info (tree field, tree fndecl, location_t loc) + : m_field (field), m_fndecl (fndecl), m_loc (loc) + {} + + void print (pretty_printer *pp) const FINAL OVERRIDE + { + pp_string (pp, "call to tainted field"); + }; + + bool update_model (region_model *, + const exploded_edge *, + region_model_context *) const FINAL OVERRIDE + { + /* No-op. */ + return true; + } + + void add_events_to_path (checker_path *emission_path, + const exploded_edge &) const FINAL OVERRIDE + { + /* Show the field in the struct declaration, e.g. + "(1) field 'store' is marked with '__attribute__((tainted_args))'" */ + emission_path->add_event + (new tainted_args_field_custom_event (m_field)); + + /* Show the callback in the initializer + e.g. + "(2) function 'gadget_dev_desc_UDC_store' used as initializer + for field 'store' marked with '__attribute__((tainted_args))'". */ + emission_path->add_event + (new tainted_args_callback_custom_event (m_loc, m_fndecl, 0, m_field)); + } + +private: + tree m_field; + tree m_fndecl; + location_t m_loc; +}; + +/* Given an initializer at LOC for FIELD marked with + '__attribute__((tainted_args))' initialized with FNDECL, add an + entrypoint to FNDECL to EG (and to its worklist) where the params to + FNDECL are marked as tainted. */ + +static void +add_tainted_args_callback (exploded_graph *eg, tree field, tree fndecl, + location_t loc) +{ + logger *logger = eg->get_logger (); + + LOG_SCOPE (logger); + + if (!gimple_has_body_p (fndecl)) + return; + + const extrinsic_state &ext_state = eg->get_ext_state (); + + function *fun = DECL_STRUCT_FUNCTION (fndecl); + gcc_assert (fun); + + program_point point + = program_point::from_function_entry (eg->get_supergraph (), fun); + program_state state (ext_state); + state.push_frame (ext_state, fun); + + if (!mark_params_as_tainted (&state, fndecl, ext_state)) + return; + + if (!state.m_valid) + return; + + exploded_node *enode = eg->get_or_create_node (point, state, NULL); + if (logger) + { + if (enode) + logger->log ("created EN %i for tainted_args %qE entrypoint", + enode->m_index, fndecl); + else + { + logger->log ("did not create enode for tainted_args %qE entrypoint", + fndecl); + return; + } + } + + tainted_args_call_info *info + = new tainted_args_call_info (field, fndecl, loc); + eg->add_edge (eg->get_origin (), enode, NULL, info); +} + +/* Callback for walk_tree for finding callbacks within initializers; + ensure that any callback initializer where the corresponding field is + marked with '__attribute__((tainted_args))' is treated as an entrypoint + to the analysis, special-casing that the inputs to the callback are + untrustworthy. */ + +static tree +add_any_callbacks (tree *tp, int *, void *data) +{ + exploded_graph *eg = (exploded_graph *)data; + if (TREE_CODE (*tp) == CONSTRUCTOR) + { + /* Find fields with the "tainted_args" attribute. + walk_tree only walks the values, not the index values; + look at the index values. */ + unsigned HOST_WIDE_INT idx; + constructor_elt *ce; + + for (idx = 0; vec_safe_iterate (CONSTRUCTOR_ELTS (*tp), idx, &ce); + idx++) + if (ce->index && TREE_CODE (ce->index) == FIELD_DECL) + if (lookup_attribute ("tainted_args", DECL_ATTRIBUTES (ce->index))) + { + tree value = ce->value; + if (TREE_CODE (value) == ADDR_EXPR + && TREE_CODE (TREE_OPERAND (value, 0)) == FUNCTION_DECL) + add_tainted_args_callback (eg, ce->index, + TREE_OPERAND (value, 0), + EXPR_LOCATION (value)); + } + } + + return NULL_TREE; +} + /* Add initial nodes to EG, with entrypoints for externally-callable functions. */ @@ -2659,6 +2964,17 @@ exploded_graph::build_initial_worklist () logger->log ("did not create enode for %qE entrypoint", fun->decl); } } + + /* Find callbacks that are reachable from global initializers. */ + varpool_node *vpnode; + FOR_EACH_VARIABLE (vpnode) + { + tree decl = vpnode->decl; + tree init = DECL_INITIAL (decl); + if (!init) + continue; + walk_tree (&init, add_any_callbacks, this, NULL); + } } /* The main loop of the analysis. |