summaryrefslogtreecommitdiff
path: root/gcc/analyzer/engine.cc
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2021-11-12 10:06:23 -0500
committerDavid Malcolm <dmalcolm@redhat.com>2022-01-13 20:18:20 -0500
commitb31cec9c22b8dfa40baefd4c2dd774477e8e04c5 (patch)
tree4675de9d964069a4cf3d2b6c630e440bb8e022ff /gcc/analyzer/engine.cc
parentad3f0d0806d64ae8ceddfbde1560d4829085202b (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.cc320
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.