blob: 57d55e7a75d14fd3301d6f6a8589282b7d5b1869 [file] [log] [blame]
/* Proof-of-concept of a -fanalyzer plugin for the Linux kernel. */
/* { dg-options "-g" } */
#include "analyzer/common.h"
#include "gcc-plugin.h"
#include "tree.h"
#include "function.h"
#include "basic-block.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "diagnostic-core.h"
#include "graphviz.h"
#include "options.h"
#include "cgraph.h"
#include "tree-dfa.h"
#include "stringpool.h"
#include "convert.h"
#include "target.h"
#include "fold-const.h"
#include "tree-pretty-print.h"
#include "diagnostics/color.h"
#include "diagnostics/metadata.h"
#include "tristate.h"
#include "bitmap.h"
#include "selftest.h"
#include "function.h"
#include "json.h"
#include "analyzer/common.h"
#include "analyzer/analyzer-logging.h"
#include "ordered-hash-map.h"
#include "options.h"
#include "cgraph.h"
#include "cfg.h"
#include "digraph.h"
#include "analyzer/supergraph.h"
#include "sbitmap.h"
#include "context.h"
#include "channels.h"
#include "analyzer/call-string.h"
#include "analyzer/program-point.h"
#include "analyzer/store.h"
#include "analyzer/region-model.h"
#include "analyzer/call-details.h"
#include "analyzer/call-info.h"
int plugin_is_GPL_compatible;
#if ENABLE_ANALYZER
namespace ana {
/* Implementation of "copy_from_user" and "copy_to_user". */
class copy_across_boundary_fn : public known_function
{
public:
virtual bool untrusted_source_p () const = 0;
virtual bool untrusted_destination_p () const = 0;
bool matches_call_types_p (const call_details &cd) const final override
{
return cd.num_args () == 3;
}
void impl_call_pre (const call_details &cd) const final override
{
region_model_manager *mgr = cd.get_manager ();
region_model *model = cd.get_model ();
region_model_context *ctxt = cd.get_ctxt ();
const svalue *dest_sval = cd.get_arg_svalue (0);
const svalue *src_sval = cd.get_arg_svalue (1);
const svalue *num_bytes_sval = cd.get_arg_svalue (2);
const region *dest_reg = model->deref_rvalue (dest_sval,
cd.get_arg_tree (0),
ctxt);
const region *src_reg = model->deref_rvalue (src_sval,
cd.get_arg_tree (1),
ctxt);
if (const svalue *bounded_sval
= model->maybe_get_copy_bounds (src_reg, num_bytes_sval))
num_bytes_sval = bounded_sval;
if (tree cst = num_bytes_sval->maybe_get_constant ())
if (zerop (cst))
{
/* No-op. */
model->update_for_zero_return (cd, true);
return;
}
const region *sized_src_reg = mgr->get_sized_region (src_reg,
NULL_TREE,
num_bytes_sval);
const svalue *copied_sval
= model->get_store_value (sized_src_reg, ctxt);
const region *sized_dest_reg = mgr->get_sized_region (dest_reg,
NULL_TREE,
num_bytes_sval);
if (ctxt)
{
/* Bifurcate state, creating a "failure" out-edge. */
ctxt->bifurcate (std::make_unique<copy_failure> (cd));
/* The "unbifurcated" state is the "success" case. */
copy_success success (cd,
sized_dest_reg,
copied_sval,
sized_src_reg,
untrusted_source_p (),
untrusted_destination_p ());
success.update_model (model, NULL, ctxt);
}
}
private:
class copy_success : public success_call_info
{
public:
copy_success (const call_details &cd,
const region *sized_dest_reg,
const svalue *copied_sval,
const region *sized_src_reg,
bool untrusted_source,
bool untrusted_destination)
: success_call_info (cd),
m_sized_dest_reg (sized_dest_reg),
m_copied_sval (copied_sval),
m_sized_src_reg (sized_src_reg),
m_untrusted_source (untrusted_source),
m_untrusted_destination (untrusted_destination)
{}
bool update_model (region_model *model,
const exploded_edge *,
region_model_context *ctxt) const final override
{
call_details cd (get_call_details (model, ctxt));
model->update_for_zero_return (cd, true);
model->set_value (m_sized_dest_reg, m_copied_sval, ctxt);
if (ctxt && m_untrusted_source)
model->mark_as_tainted (m_copied_sval, ctxt);
if (m_untrusted_destination)
model->maybe_complain_about_infoleak (m_sized_dest_reg,
m_copied_sval,
m_sized_src_reg,
ctxt);
return true;
}
const region *m_sized_dest_reg;
const svalue *m_copied_sval;
const region *m_sized_src_reg;
bool m_untrusted_source;
bool m_untrusted_destination;
};
class copy_failure : public failed_call_info
{
public:
copy_failure (const call_details &cd)
: failed_call_info (cd)
{}
bool update_model (region_model *model,
const exploded_edge *,
region_model_context *ctxt) const final override
{
call_details cd (get_call_details (model, ctxt));
model->update_for_nonzero_return (cd);
/* Leave the destination region untouched. */
return true;
}
};
};
/* "copy_from_user". */
class known_function_copy_from_user : public copy_across_boundary_fn
{
public:
bool untrusted_source_p () const final override
{
return true;
}
bool untrusted_destination_p () const final override
{
return false;
}
};
/* "copy_to_user". */
class known_function_copy_to_user : public copy_across_boundary_fn
{
public:
bool untrusted_source_p () const final override
{
return false;
}
bool untrusted_destination_p () const final override
{
return true;
}
};
/* Implementation of "__check_object_size". */
class known_function___check_object_size : public known_function
{
public:
bool matches_call_types_p (const call_details &cd) const final override
{
return cd.num_args () == 2;
}
void impl_call_pre (const call_details &) const final override
{
/* No-op. */
}
};
namespace analyzer_events = ::gcc::topics::analyzer_events;
class kernel_analyzer_events_subscriber : public analyzer_events::subscriber
{
public:
void
on_message (const analyzer_events::on_ana_init &m) final override
{
LOG_SCOPE (m.get_logger ());
m.register_known_function
("copy_from_user",
std::make_unique<known_function_copy_from_user> ());
m.register_known_function
("copy_to_user",
std::make_unique<known_function_copy_to_user> ());
m.register_known_function
("__check_object_size",
std::make_unique<known_function___check_object_size> ());
}
} kernel_sub;
} // namespace ana
#endif /* #if ENABLE_ANALYZER */
int
plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version)
{
#if ENABLE_ANALYZER
const char *plugin_name = plugin_info->base_name;
if (0)
inform (input_location, "got here; %qs", plugin_name);
g->get_channels ().analyzer_events_channel.add_subscriber (ana::kernel_sub);
#else
sorry_no_analyzer ();
#endif
return 0;
}