mirror of
https://github.com/autc04/Retro68.git
synced 2024-06-25 21:29:51 +00:00
241 lines
6.5 KiB
C++
241 lines
6.5 KiB
C++
/* An experimental state machine, for tracking exposure of sensitive
|
|
data (e.g. through logging).
|
|
Copyright (C) 2019-2022 Free Software Foundation, Inc.
|
|
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
GCC is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "tree.h"
|
|
#include "function.h"
|
|
#include "function.h"
|
|
#include "basic-block.h"
|
|
#include "gimple.h"
|
|
#include "options.h"
|
|
#include "diagnostic-path.h"
|
|
#include "diagnostic-metadata.h"
|
|
#include "function.h"
|
|
#include "json.h"
|
|
#include "analyzer/analyzer.h"
|
|
#include "diagnostic-event-id.h"
|
|
#include "analyzer/analyzer-logging.h"
|
|
#include "analyzer/sm.h"
|
|
#include "analyzer/pending-diagnostic.h"
|
|
|
|
#if ENABLE_ANALYZER
|
|
|
|
namespace ana {
|
|
|
|
namespace {
|
|
|
|
/* An experimental state machine, for tracking exposure of sensitive
|
|
data (e.g. through logging). */
|
|
|
|
class sensitive_state_machine : public state_machine
|
|
{
|
|
public:
|
|
sensitive_state_machine (logger *logger);
|
|
|
|
bool inherited_state_p () const FINAL OVERRIDE { return true; }
|
|
|
|
bool on_stmt (sm_context *sm_ctxt,
|
|
const supernode *node,
|
|
const gimple *stmt) const FINAL OVERRIDE;
|
|
|
|
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
|
|
|
/* State for "sensitive" data, such as a password. */
|
|
state_t m_sensitive;
|
|
|
|
/* Stop state, for a value we don't want to track any more. */
|
|
state_t m_stop;
|
|
|
|
private:
|
|
void warn_for_any_exposure (sm_context *sm_ctxt,
|
|
const supernode *node,
|
|
const gimple *stmt,
|
|
tree arg) const;
|
|
};
|
|
|
|
class exposure_through_output_file
|
|
: public pending_diagnostic_subclass<exposure_through_output_file>
|
|
{
|
|
public:
|
|
exposure_through_output_file (const sensitive_state_machine &sm, tree arg)
|
|
: m_sm (sm), m_arg (arg)
|
|
{}
|
|
|
|
const char *get_kind () const FINAL OVERRIDE
|
|
{
|
|
return "exposure_through_output_file";
|
|
}
|
|
|
|
bool operator== (const exposure_through_output_file &other) const
|
|
{
|
|
return same_tree_p (m_arg, other.m_arg);
|
|
}
|
|
|
|
int get_controlling_option () const FINAL OVERRIDE
|
|
{
|
|
return OPT_Wanalyzer_exposure_through_output_file;
|
|
}
|
|
|
|
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
|
{
|
|
diagnostic_metadata m;
|
|
/* CWE-532: Information Exposure Through Log Files */
|
|
m.add_cwe (532);
|
|
return warning_meta (rich_loc, m, get_controlling_option (),
|
|
"sensitive value %qE written to output file",
|
|
m_arg);
|
|
}
|
|
|
|
label_text describe_state_change (const evdesc::state_change &change)
|
|
FINAL OVERRIDE
|
|
{
|
|
if (change.m_new_state == m_sm.m_sensitive)
|
|
{
|
|
m_first_sensitive_event = change.m_event_id;
|
|
return change.formatted_print ("sensitive value acquired here");
|
|
}
|
|
return label_text ();
|
|
}
|
|
|
|
label_text describe_call_with_state (const evdesc::call_with_state &info)
|
|
FINAL OVERRIDE
|
|
{
|
|
if (info.m_state == m_sm.m_sensitive)
|
|
return info.formatted_print
|
|
("passing sensitive value %qE in call to %qE from %qE",
|
|
info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
|
|
return label_text ();
|
|
}
|
|
|
|
label_text describe_return_of_state (const evdesc::return_of_state &info)
|
|
FINAL OVERRIDE
|
|
{
|
|
if (info.m_state == m_sm.m_sensitive)
|
|
return info.formatted_print ("returning sensitive value to %qE from %qE",
|
|
info.m_caller_fndecl, info.m_callee_fndecl);
|
|
return label_text ();
|
|
}
|
|
|
|
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
|
{
|
|
if (m_first_sensitive_event.known_p ())
|
|
return ev.formatted_print ("sensitive value %qE written to output file"
|
|
"; acquired at %@",
|
|
m_arg, &m_first_sensitive_event);
|
|
else
|
|
return ev.formatted_print ("sensitive value %qE written to output file",
|
|
m_arg);
|
|
}
|
|
|
|
private:
|
|
const sensitive_state_machine &m_sm;
|
|
tree m_arg;
|
|
diagnostic_event_id_t m_first_sensitive_event;
|
|
};
|
|
|
|
/* sensitive_state_machine's ctor. */
|
|
|
|
sensitive_state_machine::sensitive_state_machine (logger *logger)
|
|
: state_machine ("sensitive", logger)
|
|
{
|
|
m_sensitive = add_state ("sensitive");
|
|
m_stop = add_state ("stop");
|
|
}
|
|
|
|
/* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
|
|
state. */
|
|
|
|
void
|
|
sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
|
|
const supernode *node,
|
|
const gimple *stmt,
|
|
tree arg) const
|
|
{
|
|
if (sm_ctxt->get_state (stmt, arg) == m_sensitive)
|
|
{
|
|
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
|
sm_ctxt->warn (node, stmt, arg,
|
|
new exposure_through_output_file (*this, diag_arg));
|
|
}
|
|
}
|
|
|
|
/* Implementation of state_machine::on_stmt vfunc for
|
|
sensitive_state_machine. */
|
|
|
|
bool
|
|
sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
|
|
const supernode *node,
|
|
const gimple *stmt) const
|
|
{
|
|
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
|
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
|
{
|
|
if (is_named_call_p (callee_fndecl, "getpass", call, 1))
|
|
{
|
|
tree lhs = gimple_call_lhs (call);
|
|
if (lhs)
|
|
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
|
|
return true;
|
|
}
|
|
else if (is_named_call_p (callee_fndecl, "fprintf")
|
|
|| is_named_call_p (callee_fndecl, "printf"))
|
|
{
|
|
/* Handle a match at any position in varargs. */
|
|
for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
|
|
{
|
|
tree arg = gimple_call_arg (call, idx);
|
|
warn_for_any_exposure (sm_ctxt, node, stmt, arg);
|
|
}
|
|
return true;
|
|
}
|
|
else if (is_named_call_p (callee_fndecl, "fwrite", call, 4))
|
|
{
|
|
tree arg = gimple_call_arg (call, 0);
|
|
warn_for_any_exposure (sm_ctxt, node, stmt, arg);
|
|
return true;
|
|
}
|
|
// TODO: ...etc. This is just a proof-of-concept at this point.
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
/* Internal interface to this file. */
|
|
|
|
state_machine *
|
|
make_sensitive_state_machine (logger *logger)
|
|
{
|
|
return new sensitive_state_machine (logger);
|
|
}
|
|
|
|
} // namespace ana
|
|
|
|
#endif /* #if ENABLE_ANALYZER */
|