@@ -72,6 +72,8 @@ on_finish_translation_unit (const translation_unit &tu)
maybe_stash_named_constant (tu, "O_ACCMODE");
maybe_stash_named_constant (tu, "O_RDONLY");
maybe_stash_named_constant (tu, "O_WRONLY");
+ maybe_stash_named_constant (tu, "SOCK_STREAM");
+ maybe_stash_named_constant (tu, "SOCK_DGRAM");
}
/* Lookup NAME in the named constants stashed when the frontend TU finished.
@@ -86,6 +86,14 @@ Wanalyzer-fd-leak
Common Var(warn_analyzer_fd_leak) Init(1) Warning
Warn about code paths in which a file descriptor is not closed.
+Wanalyzer-fd-phase-mismatch
+Common Var(warn_analyzer_fd_phase_mismatch) Init(1) Warning
+Warn about code paths in which an operation is attempted in the wrong phase of a file descriptor's lifetime.
+
+Wanalyzer-fd-type-mismatch
+Common Var(warn_analyzer_fd_type_mismatch) Init(1) Warning
+Warn about code paths in which an operation is attempted on the wrong type of file descriptor.
+
Wanalyzer-fd-use-after-close
Common Var(warn_analyzer_fd_use_after_close) Init(1) Warning
Warn about code paths in which a read or write is performed on a closed file descriptor.
@@ -206,25 +206,6 @@ impl_region_model_context::terminate_path ()
return m_path_ctxt->terminate_path ();
}
-bool
-impl_region_model_context::get_state_map_by_name (const char *name,
- sm_state_map **out_smap,
- const state_machine **out_sm,
- unsigned *out_sm_idx)
-{
- if (!m_new_state)
- return false;
-
- unsigned sm_idx;
- if (!m_ext_state.get_sm_idx_by_name (name, &sm_idx))
- return false;
-
- *out_smap = m_new_state->m_checker_states[sm_idx];
- *out_sm = &m_ext_state.get_sm (sm_idx);
- *out_sm_idx = sm_idx;
- return true;
-}
-
/* struct setjmp_record. */
int
@@ -527,6 +508,47 @@ public:
bool m_unknown_side_effects;
};
+bool
+impl_region_model_context::
+get_state_map_by_name (const char *name,
+ sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context)
+{
+ if (!m_new_state)
+ return false;
+
+ unsigned sm_idx;
+ if (!m_ext_state.get_sm_idx_by_name (name, &sm_idx))
+ return false;
+
+ const state_machine *sm = &m_ext_state.get_sm (sm_idx);
+ sm_state_map *new_smap = m_new_state->m_checker_states[sm_idx];
+
+ *out_smap = new_smap;
+ *out_sm = sm;
+ if (out_sm_idx)
+ *out_sm_idx = sm_idx;
+ if (out_sm_context)
+ {
+ const sm_state_map *old_smap = m_old_state->m_checker_states[sm_idx];
+ *out_sm_context
+ = make_unique<impl_sm_context> (*m_eg,
+ sm_idx,
+ *sm,
+ m_enode_for_diag,
+ m_old_state,
+ m_new_state,
+ old_smap,
+ new_smap,
+ m_path_ctxt,
+ m_stmt_finder,
+ false);
+ }
+ return true;
+}
+
/* Subclass of stmt_finder for finding the best stmt to report the leak at,
given the emission path. */
@@ -96,10 +96,12 @@ class impl_region_model_context : public region_model_context
{
return &m_ext_state;
}
- bool get_state_map_by_name (const char *name,
- sm_state_map **out_smap,
- const state_machine **out_sm,
- unsigned *out_sm_idx) override;
+ bool
+ get_state_map_by_name (const char *name,
+ sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context) override;
const gimple *get_stmt () const override { return m_stmt; }
@@ -407,6 +407,66 @@ region_model::impl_call_analyzer_get_unknown_ptr (const call_details &cd)
cd.maybe_set_lhs (ptr_sval);
}
+/* Handle the on_call_post part of "accept". */
+
+void
+region_model::impl_call_accept (const call_details &cd)
+{
+ class outcome_of_accept : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_accept (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_accept (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_accept. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_accept> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_accept> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
+/* Handle the on_call_post part of "bind". */
+
+void
+region_model::impl_call_bind (const call_details &cd)
+{
+ class outcome_of_bind : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_bind (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_bind (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_bind. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_bind> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_bind> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
/* Handle the on_call_pre part of "__builtin_expect" etc. */
void
@@ -441,6 +501,36 @@ region_model::impl_call_calloc (const call_details &cd)
}
}
+/* Handle the on_call_post part of "connect". */
+
+void
+region_model::impl_call_connect (const call_details &cd)
+{
+ class outcome_of_connect : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_connect (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_connect (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_connect. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_connect> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_connect> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
/* Handle the on_call_pre part of "__errno_location". */
void
@@ -543,6 +633,36 @@ region_model::impl_call_free (const call_details &cd)
}
}
+/* Handle the on_call_post part of "listen". */
+
+void
+region_model::impl_call_listen (const call_details &cd)
+{
+ class outcome_of_listen : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_listen (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_listen (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_listen. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_listen> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_listen> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
/* Handle the on_call_pre part of "malloc". */
void
@@ -1055,6 +1175,36 @@ region_model::impl_call_realloc (const call_details &cd)
}
}
+/* Handle the on_call_post part of "socket". */
+
+void
+region_model::impl_call_socket (const call_details &cd)
+{
+ class outcome_of_socket : public succeed_or_fail_call_info
+ {
+ public:
+ outcome_of_socket (const call_details &cd, bool success)
+ : succeed_or_fail_call_info (cd, success)
+ {}
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ return cd.get_model ()->on_socket (cd, m_success);
+ }
+ };
+
+ /* Body of region_model::impl_call_socket. */
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_socket> (cd, false));
+ cd.get_ctxt ()->bifurcate (make_unique<outcome_of_socket> (cd, true));
+ cd.get_ctxt ()->terminate_path ();
+ }
+}
+
/* Handle the on_call_post part of "strchr" and "__builtin_strchr". */
void
@@ -2292,6 +2292,11 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
impl_call_realloc (cd);
return false;
}
+ else if (is_named_call_p (callee_fndecl, "bind", call, 3))
+ {
+ /* Handle in "on_call_post". */
+ return false;
+ }
else if (is_named_call_p (callee_fndecl, "__errno_location", call, 0))
{
impl_call_errno_location (cd);
@@ -2421,12 +2426,37 @@ region_model::on_call_post (const gcall *call,
impl_call_operator_delete (cd);
return;
}
+ else if (is_named_call_p (callee_fndecl, "accept", call, 3))
+ {
+ impl_call_accept (cd);
+ return;
+ }
+ else if (is_named_call_p (callee_fndecl, "bind", call, 3))
+ {
+ impl_call_bind (cd);
+ return;
+ }
+ else if (is_named_call_p (callee_fndecl, "connect", call, 3))
+ {
+ impl_call_connect (cd);
+ return;
+ }
+ else if (is_named_call_p (callee_fndecl, "listen", call, 2))
+ {
+ impl_call_listen (cd);
+ return;
+ }
else if (is_pipe_call_p (callee_fndecl, "pipe", call, 1)
|| is_pipe_call_p (callee_fndecl, "pipe2", call, 2))
{
impl_call_pipe (cd);
return;
}
+ else if (is_named_call_p (callee_fndecl, "socket", call, 3))
+ {
+ impl_call_socket (cd);
+ return;
+ }
else if (is_named_call_p (callee_fndecl, "strchr", call, 2)
&& POINTER_TYPE_P (cd.get_arg_type (0)))
{
@@ -338,6 +338,7 @@ class region_model
void purge_state_involving (const svalue *sval, region_model_context *ctxt);
/* Specific handling for on_call_pre. */
+ void impl_call_accept (const call_details &cd);
void impl_call_alloca (const call_details &cd);
void impl_call_analyzer_describe (const gcall *call,
region_model_context *ctxt);
@@ -349,20 +350,24 @@ class region_model
void impl_call_analyzer_eval (const gcall *call,
region_model_context *ctxt);
void impl_call_analyzer_get_unknown_ptr (const call_details &cd);
+ void impl_call_bind (const call_details &cd);
void impl_call_builtin_expect (const call_details &cd);
void impl_call_calloc (const call_details &cd);
+ void impl_call_connect (const call_details &cd);
void impl_call_errno_location (const call_details &cd);
bool impl_call_error (const call_details &cd, unsigned min_args,
bool *out_terminate_path);
void impl_call_fgets (const call_details &cd);
void impl_call_fread (const call_details &cd);
void impl_call_free (const call_details &cd);
+ void impl_call_listen (const call_details &cd);
void impl_call_malloc (const call_details &cd);
void impl_call_memcpy (const call_details &cd);
void impl_call_memset (const call_details &cd);
void impl_call_pipe (const call_details &cd);
void impl_call_putenv (const call_details &cd);
void impl_call_realloc (const call_details &cd);
+ void impl_call_socket (const call_details &cd);
void impl_call_strchr (const call_details &cd);
void impl_call_strcpy (const call_details &cd);
void impl_call_strlen (const call_details &cd);
@@ -551,6 +556,11 @@ class region_model
/* Implemented in sm-fd.cc */
void mark_as_valid_fd (const svalue *sval, region_model_context *ctxt);
+ bool on_socket (const call_details &cd, bool successful);
+ bool on_bind (const call_details &cd, bool successful);
+ bool on_listen (const call_details &cd, bool successful);
+ bool on_accept (const call_details &cd, bool successful);
+ bool on_connect (const call_details &cd, bool successful);
/* Implemented in sm-malloc.cc */
void on_realloc_with_move (const call_details &cd,
@@ -561,7 +571,16 @@ class region_model
void mark_as_tainted (const svalue *sval,
region_model_context *ctxt);
- private:
+ bool add_constraint (const svalue *lhs,
+ enum tree_code op,
+ const svalue *rhs,
+ region_model_context *ctxt);
+
+ const svalue *check_for_poison (const svalue *sval,
+ tree expr,
+ region_model_context *ctxt) const;
+
+private:
const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
@@ -574,10 +593,6 @@ class region_model
const known_function *get_known_function (tree fndecl) const;
- bool add_constraint (const svalue *lhs,
- enum tree_code op,
- const svalue *rhs,
- region_model_context *ctxt);
bool add_constraints_from_binop (const svalue *outer_lhs,
enum tree_code outer_op,
const svalue *outer_rhs,
@@ -608,9 +623,6 @@ class region_model
bool called_from_main_p () const;
const svalue *get_initial_value_for_global (const region *reg) const;
- const svalue *check_for_poison (const svalue *sval,
- tree expr,
- region_model_context *ctxt) const;
const region * get_region_for_poisoned_expr (tree expr) const;
void check_dynamic_size_for_taint (enum memory_space mem_space,
@@ -744,30 +756,33 @@ class region_model_context
/* Hook for clients to access the a specific state machine in
any underlying program_state. */
- virtual bool get_state_map_by_name (const char *name,
- sm_state_map **out_smap,
- const state_machine **out_sm,
- unsigned *out_sm_idx) = 0;
+ virtual bool
+ get_state_map_by_name (const char *name,
+ sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context) = 0;
/* Precanned ways for clients to access specific state machines. */
bool get_fd_map (sm_state_map **out_smap,
const state_machine **out_sm,
- unsigned *out_sm_idx)
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context)
{
return get_state_map_by_name ("file-descriptor", out_smap, out_sm,
- out_sm_idx);
+ out_sm_idx, out_sm_context);
}
bool get_malloc_map (sm_state_map **out_smap,
const state_machine **out_sm,
unsigned *out_sm_idx)
{
- return get_state_map_by_name ("malloc", out_smap, out_sm, out_sm_idx);
+ return get_state_map_by_name ("malloc", out_smap, out_sm, out_sm_idx, NULL);
}
bool get_taint_map (sm_state_map **out_smap,
const state_machine **out_sm,
unsigned *out_sm_idx)
{
- return get_state_map_by_name ("taint", out_smap, out_sm, out_sm_idx);
+ return get_state_map_by_name ("taint", out_smap, out_sm, out_sm_idx, NULL);
}
/* Get the current statement, if any. */
@@ -818,7 +833,8 @@ public:
bool get_state_map_by_name (const char *,
sm_state_map **,
const state_machine **,
- unsigned *) override
+ unsigned *,
+ std::unique_ptr<sm_context> *) override
{
return false;
}
@@ -940,9 +956,12 @@ class region_model_context_decorator : public region_model_context
bool get_state_map_by_name (const char *name,
sm_state_map **out_smap,
const state_machine **out_sm,
- unsigned *out_sm_idx) override
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context)
+ override
{
- return m_inner->get_state_map_by_name (name, out_smap, out_sm, out_sm_idx);
+ return m_inner->get_state_map_by_name (name, out_smap, out_sm, out_sm_idx,
+ out_sm_context);
}
const gimple *get_stmt () const override
@@ -45,6 +45,8 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/region-model.h"
#include "bitmap.h"
#include "analyzer/program-state.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/analyzer-language.h"
#if ENABLE_ANALYZER
@@ -76,6 +78,17 @@ enum dup
DUP_3
};
+/* Enum for use by -Wanalyzer-fd-phase-mismatch. */
+
+enum expected_phase
+{
+ EXPECTED_PHASE_CAN_TRANSFER, /* can "read"/"write". */
+ EXPECTED_PHASE_CAN_BIND,
+ EXPECTED_PHASE_CAN_LISTEN,
+ EXPECTED_PHASE_CAN_ACCEPT,
+ EXPECTED_PHASE_CAN_CONNECT
+};
+
class fd_state_machine : public state_machine
{
public:
@@ -116,6 +129,9 @@ public:
bool is_unchecked_fd_p (state_t s) const;
bool is_valid_fd_p (state_t s) const;
+ bool is_socket_fd_p (state_t s) const;
+ bool is_datagram_socket_fd_p (state_t s) const;
+ bool is_stream_socket_fd_p (state_t s) const;
bool is_closed_fd_p (state_t s) const;
bool is_constant_fd_p (state_t s) const;
bool is_readonly_fd_p (state_t s) const;
@@ -130,6 +146,27 @@ public:
const svalue *fd_sval,
const extrinsic_state &ext_state) const;
+ bool on_socket (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+ bool on_bind (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+ bool on_listen (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+ bool on_accept (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+ bool on_connect (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const;
+
/* State for a constant file descriptor (>= 0) */
state_t m_constant_fd;
@@ -156,6 +193,29 @@ public:
/* State for a file descriptor that has been closed. */
state_t m_closed;
+ /* States for FDs relating to socket APIs. */
+
+ /* Result of successful "socket" with SOCK_DGRAM. */
+ state_t m_new_datagram_socket;
+ /* Result of successful "socket" with SOCK_STREAM. */
+ state_t m_new_stream_socket;
+ /* Result of successful "socket" with unknown type. */
+ state_t m_new_unknown_socket;
+
+ /* The above after a successful call to "bind". */
+ state_t m_bound_datagram_socket;
+ state_t m_bound_stream_socket;
+ state_t m_bound_unknown_socket;
+
+ /* A bound socket after a successful call to "listen" (stream or unknown). */
+ state_t m_listening_stream_socket;
+
+ /* (i) the new FD as a result of a succesful call to "accept" on a
+ listening socket (via a passive open), or
+ (ii) an active socket after a successful call to "connect"
+ (via an active open). */
+ state_t m_connected_stream_socket;
+
/* State for a file descriptor that we do not want to track anymore . */
state_t m_stop;
@@ -163,6 +223,8 @@ public:
tree m_O_ACCMODE;
tree m_O_RDONLY;
tree m_O_WRONLY;
+ tree m_SOCK_STREAM;
+ tree m_SOCK_DGRAM;
private:
void on_open (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
@@ -195,6 +257,23 @@ private:
void check_for_dup (sm_context *sm_ctxt, const supernode *node,
const gimple *stmt, const gcall *call, const tree callee_fndecl,
enum dup kind) const;
+
+ state_t get_state_for_socket_type (const svalue *socket_type_sval) const;
+
+ bool check_for_socket_fd (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const svalue *fd_sval,
+ const supernode *node,
+ state_t old_state,
+ bool *complained = NULL) const;
+ bool check_for_new_socket_fd (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const svalue *fd_sval,
+ const supernode *node,
+ state_t old_state,
+ enum expected_phase expected_phase) const;
};
/* Base diagnostic class relative to fd_state_machine. */
@@ -214,9 +293,7 @@ public:
label_text
describe_state_change (const evdesc::state_change &change) override
{
- if (change.m_old_state == m_sm.get_start_state ()
- && (m_sm.is_unchecked_fd_p (change.m_new_state)
- || m_sm.is_valid_fd_p (change.m_new_state)))
+ if (change.m_old_state == m_sm.get_start_state ())
{
if (change.m_new_state == m_sm.m_unchecked_read_write
|| change.m_new_state == m_sm.m_valid_read_write)
@@ -229,8 +306,32 @@ public:
if (change.m_new_state == m_sm.m_unchecked_write_only
|| change.m_new_state == m_sm.m_valid_write_only)
return change.formatted_print ("opened here as write-only");
+
+ if (change.m_new_state == m_sm.m_new_datagram_socket)
+ return change.formatted_print ("datagram socket created here");
+
+ if (change.m_new_state == m_sm.m_new_stream_socket)
+ return change.formatted_print ("stream socket created here");
+
+ if (change.m_new_state == m_sm.m_new_unknown_socket
+ || change.m_new_state == m_sm.m_connected_stream_socket)
+ return change.formatted_print ("socket created here");
}
+ if (change.m_new_state == m_sm.m_bound_datagram_socket)
+ return change.formatted_print ("datagram socket bound here");
+
+ if (change.m_new_state == m_sm.m_bound_stream_socket)
+ return change.formatted_print ("stream socket bound here");
+
+ if (change.m_new_state == m_sm.m_bound_unknown_socket
+ || change.m_new_state == m_sm.m_connected_stream_socket)
+ return change.formatted_print ("socket bound here");
+
+ if (change.m_new_state == m_sm.m_listening_stream_socket)
+ return change.formatted_print
+ ("stream socket marked as passive here via %qs", "listen");
+
if (change.m_new_state == m_sm.m_closed)
return change.formatted_print ("closed here");
@@ -263,7 +364,10 @@ public:
const evdesc::state_change &change) const final override
{
if (change.m_old_state == m_sm.get_start_state ()
- && (m_sm.is_unchecked_fd_p (change.m_new_state)))
+ && (m_sm.is_unchecked_fd_p (change.m_new_state)
+ || change.m_new_state == m_sm.m_new_datagram_socket
+ || change.m_new_state == m_sm.m_new_stream_socket
+ || change.m_new_state == m_sm.m_new_unknown_socket))
return diagnostic_event::meaning (diagnostic_event::VERB_acquire,
diagnostic_event::NOUN_resource);
if (change.m_new_state == m_sm.m_closed)
@@ -680,6 +784,289 @@ private:
diagnostic_event_id_t m_first_open_event;
};
+/* Concrete pending_diagnostic subclass for -Wanalyzer-fd-phase-mismatch. */
+
+class fd_phase_mismatch : public fd_param_diagnostic
+{
+public:
+ fd_phase_mismatch (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl,
+ state_machine::state_t actual_state,
+ enum expected_phase expected_phase)
+ : fd_param_diagnostic (sm, arg, callee_fndecl),
+ m_actual_state (actual_state),
+ m_expected_phase (expected_phase)
+ {
+ gcc_assert (m_sm.is_socket_fd_p (actual_state));
+ switch (expected_phase)
+ {
+ case EXPECTED_PHASE_CAN_TRANSFER:
+ gcc_assert (actual_state == m_sm.m_new_stream_socket
+ || actual_state == m_sm.m_bound_stream_socket
+ || actual_state == m_sm.m_listening_stream_socket);
+ break;
+ case EXPECTED_PHASE_CAN_BIND:
+ gcc_assert (actual_state == m_sm.m_bound_datagram_socket
+ || actual_state == m_sm.m_bound_stream_socket
+ || actual_state == m_sm.m_bound_unknown_socket
+ || actual_state == m_sm.m_connected_stream_socket
+ || actual_state == m_sm.m_listening_stream_socket);
+ break;
+ case EXPECTED_PHASE_CAN_LISTEN:
+ gcc_assert (actual_state == m_sm.m_new_stream_socket
+ || actual_state == m_sm.m_new_unknown_socket
+ || actual_state == m_sm.m_connected_stream_socket);
+ break;
+ case EXPECTED_PHASE_CAN_ACCEPT:
+ gcc_assert (actual_state == m_sm.m_new_stream_socket
+ || actual_state == m_sm.m_new_unknown_socket
+ || actual_state == m_sm.m_bound_stream_socket
+ || actual_state == m_sm.m_bound_unknown_socket
+ || actual_state == m_sm.m_connected_stream_socket);
+ break;
+ case EXPECTED_PHASE_CAN_CONNECT:
+ gcc_assert (actual_state == m_sm.m_bound_datagram_socket
+ || actual_state == m_sm.m_bound_stream_socket
+ || actual_state == m_sm.m_bound_unknown_socket
+ || actual_state == m_sm.m_listening_stream_socket
+ || actual_state == m_sm.m_connected_stream_socket);
+ break;
+ }
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_phase_mismatch";
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const final override
+ {
+ const fd_phase_mismatch &sub_other = (const fd_phase_mismatch &)base_other;
+ if (!fd_param_diagnostic ::subclass_equal_p (sub_other))
+ return false;
+ return (m_actual_state == sub_other.m_actual_state
+ && m_expected_phase == sub_other.m_expected_phase);
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_phase_mismatch;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ /* CWE-666: Operation on Resource in Wrong Phase of Lifetime. */
+ diagnostic_metadata m;
+ m.add_cwe (666);
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on file descriptor %qE in wrong phase",
+ m_callee_fndecl, m_arg);
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ switch (m_expected_phase)
+ {
+ case EXPECTED_PHASE_CAN_TRANSFER:
+ {
+ if (m_actual_state == m_sm.m_new_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a stream socket to be connected via %qs"
+ " but %qE has not yet been bound",
+ m_callee_fndecl, "accept", m_arg);
+ if (m_actual_state == m_sm.m_bound_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a stream socket to be connected via %qs"
+ " but %qE is not yet listening",
+ m_callee_fndecl, "accept", m_arg);
+ if (m_actual_state == m_sm.m_listening_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a stream socket to be connected via"
+ " the return value of %qs"
+ " but %qE is listening; wrong file descriptor?",
+ m_callee_fndecl, "accept", m_arg);
+ }
+ break;
+ case EXPECTED_PHASE_CAN_BIND:
+ {
+ if (m_actual_state == m_sm.m_bound_datagram_socket
+ || m_actual_state == m_sm.m_bound_stream_socket
+ || m_actual_state == m_sm.m_bound_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor"
+ " but %qE has already been bound",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_connected_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor"
+ " but %qE is already connected",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_listening_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor"
+ " but %qE is already listening",
+ m_callee_fndecl, m_arg);
+ }
+ break;
+ case EXPECTED_PHASE_CAN_LISTEN:
+ {
+ if (m_actual_state == m_sm.m_new_stream_socket
+ || m_actual_state == m_sm.m_new_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a bound stream socket file descriptor"
+ " but %qE has not yet been bound",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_connected_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a bound stream socket file descriptor"
+ " but %qE is connected",
+ m_callee_fndecl, m_arg);
+ }
+ break;
+ case EXPECTED_PHASE_CAN_ACCEPT:
+ {
+ if (m_actual_state == m_sm.m_new_stream_socket
+ || m_actual_state == m_sm.m_new_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a listening stream socket file descriptor"
+ " but %qE has not yet been bound",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_bound_stream_socket
+ || m_actual_state == m_sm.m_bound_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a listening stream socket file descriptor"
+ " whereas %qE is bound but not yet listening",
+ m_callee_fndecl, m_arg);
+ if (m_actual_state == m_sm.m_connected_stream_socket)
+ return ev.formatted_print
+ ("%qE expects a listening stream socket file descriptor"
+ " but %qE is connected",
+ m_callee_fndecl, m_arg);
+ }
+ break;
+ case EXPECTED_PHASE_CAN_CONNECT:
+ {
+ if (m_actual_state == m_sm.m_bound_datagram_socket
+ || m_actual_state == m_sm.m_bound_stream_socket
+ || m_actual_state == m_sm.m_bound_unknown_socket)
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor but %qE is bound",
+ m_callee_fndecl, m_arg);
+ else
+ return ev.formatted_print
+ ("%qE expects a new socket file descriptor", m_callee_fndecl);
+ }
+ break;
+ }
+ gcc_unreachable ();
+ }
+
+private:
+ state_machine::state_t m_actual_state;
+ enum expected_phase m_expected_phase;
+};
+
+/* Enum for use by -Wanalyzer-fd-type-mismatch. */
+
+enum expected_type
+{
+ EXPECTED_TYPE_SOCKET,
+ EXPECTED_TYPE_STREAM_SOCKET
+};
+
+/* Concrete pending_diagnostic subclass for -Wanalyzer-fd-type-mismatch. */
+
+class fd_type_mismatch : public fd_param_diagnostic
+{
+public:
+ fd_type_mismatch (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl,
+ state_machine::state_t actual_state,
+ enum expected_type expected_type)
+ : fd_param_diagnostic (sm, arg, callee_fndecl),
+ m_actual_state (actual_state),
+ m_expected_type (expected_type)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_type_mismatch";
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const final override
+ {
+ const fd_type_mismatch &sub_other = (const fd_type_mismatch &)base_other;
+ if (!fd_param_diagnostic ::subclass_equal_p (sub_other))
+ return false;
+ return (m_actual_state == sub_other.m_actual_state
+ && m_expected_type == sub_other.m_expected_type);
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_type_mismatch;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ switch (m_expected_type)
+ {
+ default:
+ gcc_unreachable ();
+ case EXPECTED_TYPE_SOCKET:
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on non-socket file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ case EXPECTED_TYPE_STREAM_SOCKET:
+ if (m_sm.is_datagram_socket_fd_p (m_actual_state))
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on datagram socket file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ else
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on non-stream-socket file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ }
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ switch (m_expected_type)
+ {
+ default:
+ break;
+ gcc_unreachable ();
+ case EXPECTED_TYPE_SOCKET:
+ case EXPECTED_TYPE_STREAM_SOCKET:
+ if (!m_sm.is_socket_fd_p (m_actual_state))
+ return ev.formatted_print ("%qE expects a socket file descriptor"
+ " but %qE is not a socket",
+ m_callee_fndecl, m_arg);
+ }
+ gcc_assert (m_expected_type == EXPECTED_TYPE_STREAM_SOCKET);
+ gcc_assert (m_sm.is_datagram_socket_fd_p (m_actual_state));
+ return ev.formatted_print
+ ("%qE expects a stream socket file descriptor"
+ " but %qE is a datagram socket",
+ m_callee_fndecl, m_arg);
+ }
+
+private:
+ state_machine::state_t m_actual_state;
+ enum expected_type m_expected_type;
+};
+
fd_state_machine::fd_state_machine (logger *logger)
: state_machine ("file-descriptor", logger),
m_constant_fd (add_state ("fd-constant")),
@@ -691,10 +1078,20 @@ fd_state_machine::fd_state_machine (logger *logger)
m_valid_write_only (add_state ("fd-valid-write-only")),
m_invalid (add_state ("fd-invalid")),
m_closed (add_state ("fd-closed")),
+ m_new_datagram_socket (add_state ("fd-new-datagram-socket")),
+ m_new_stream_socket (add_state ("fd-new-stream-socket")),
+ m_new_unknown_socket (add_state ("fd-new-unknown-socket")),
+ m_bound_datagram_socket (add_state ("fd-bound-datagram-socket")),
+ m_bound_stream_socket (add_state ("fd-bound-stream-socket")),
+ m_bound_unknown_socket (add_state ("fd-bound-unknown-socket")),
+ m_listening_stream_socket (add_state ("fd-listening-stream-socket")),
+ m_connected_stream_socket (add_state ("fd-connected-stream-socket")),
m_stop (add_state ("fd-stop")),
m_O_ACCMODE (get_stashed_constant_by_name ("O_ACCMODE")),
m_O_RDONLY (get_stashed_constant_by_name ("O_RDONLY")),
- m_O_WRONLY (get_stashed_constant_by_name ("O_WRONLY"))
+ m_O_WRONLY (get_stashed_constant_by_name ("O_WRONLY")),
+ m_SOCK_STREAM (get_stashed_constant_by_name ("SOCK_STREAM")),
+ m_SOCK_DGRAM (get_stashed_constant_by_name ("SOCK_DGRAM"))
{
}
@@ -714,6 +1111,39 @@ fd_state_machine::is_valid_fd_p (state_t s) const
|| s == m_valid_write_only);
}
+bool
+fd_state_machine::is_socket_fd_p (state_t s) const
+{
+ return (s == m_new_datagram_socket
+ || s == m_new_stream_socket
+ || s == m_new_unknown_socket
+ || s == m_bound_datagram_socket
+ || s == m_bound_stream_socket
+ || s == m_bound_unknown_socket
+ || s == m_listening_stream_socket
+ || s == m_connected_stream_socket);
+}
+
+bool
+fd_state_machine::is_datagram_socket_fd_p (state_t s) const
+{
+ return (s == m_new_datagram_socket
+ || s == m_new_unknown_socket
+ || s == m_bound_datagram_socket
+ || s == m_bound_unknown_socket);
+}
+
+bool
+fd_state_machine::is_stream_socket_fd_p (state_t s) const
+{
+ return (s == m_new_stream_socket
+ || s == m_new_unknown_socket
+ || s == m_bound_stream_socket
+ || s == m_bound_unknown_socket
+ || s == m_listening_stream_socket
+ || s == m_connected_stream_socket);
+}
+
enum access_mode
fd_state_machine::get_access_mode_from_flag (int flag) const
{
@@ -1079,6 +1509,14 @@ fd_state_machine::on_close (sm_context *sm_ctxt, const supernode *node,
sm_ctxt->on_transition (node, stmt, arg, m_valid_read_only, m_closed);
sm_ctxt->on_transition (node, stmt, arg, m_valid_write_only, m_closed);
sm_ctxt->on_transition (node, stmt, arg, m_constant_fd, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_new_datagram_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_new_stream_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_new_unknown_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_bound_datagram_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_bound_stream_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_bound_unknown_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_listening_stream_socket, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_connected_stream_socket, m_closed);
if (is_closed_fd_p (state))
{
@@ -1121,7 +1559,22 @@ fd_state_machine::check_for_open_fd (
else
{
- if (!(is_valid_fd_p (state) || state == m_start || state == m_stop))
+ if (state == m_new_stream_socket
+ || state == m_bound_stream_socket
+ || state == m_listening_stream_socket)
+ /* Complain about fncall on socket in wrong phase. */
+ sm_ctxt->warn
+ (node, stmt, arg,
+ make_unique<fd_phase_mismatch> (*this, diag_arg,
+ callee_fndecl,
+ state,
+ EXPECTED_PHASE_CAN_TRANSFER));
+ else if (!(is_valid_fd_p (state)
+ || state == m_new_datagram_socket
+ || state == m_bound_unknown_socket
+ || state == m_connected_stream_socket
+ || state == m_start
+ || state == m_stop))
{
if (!is_constant_fd_p (state))
sm_ctxt->warn (
@@ -1157,6 +1610,529 @@ fd_state_machine::check_for_open_fd (
}
}
+static bool
+add_constraint_ge_zero (region_model *model,
+ const svalue *fd_sval,
+ region_model_context *ctxt)
+{
+ const svalue *zero
+ = model->get_manager ()->get_or_create_int_cst (integer_type_node, 0);
+ return model->add_constraint (fd_sval, GE_EXPR, zero, ctxt);
+}
+
+/* Get the state for a new socket type based on SOCKET_TYPE_SVAL,
+ a SOCK_* value. */
+
+state_machine::state_t
+fd_state_machine::
+get_state_for_socket_type (const svalue *socket_type_sval) const
+{
+ if (tree socket_type_cst = socket_type_sval->maybe_get_constant ())
+ {
+ /* Attempt to use SOCK_* constants stashed from the frontend. */
+ if (tree_int_cst_equal (socket_type_cst, m_SOCK_STREAM))
+ return m_new_stream_socket;
+ if (tree_int_cst_equal (socket_type_cst, m_SOCK_DGRAM))
+ return m_new_datagram_socket;
+ }
+
+ /* Unrecognized constant, or a symbolic "type" value. */
+ return m_new_unknown_socket;
+}
+
+/* Update the model and fd state for an outcome of a call to "socket",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_socket (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (stmt);
+ region_model *model = cd.get_model ();
+
+ if (successful)
+ {
+ if (gimple_call_lhs (stmt))
+ {
+ conjured_purge p (model, cd.get_ctxt ());
+ region_model_manager *mgr = model->get_manager ();
+ const svalue *new_fd
+ = mgr->get_or_create_conjured_svalue (integer_type_node,
+ stmt,
+ cd.get_lhs_region (),
+ p);
+ if (!add_constraint_ge_zero (model, new_fd, cd.get_ctxt ()))
+ return false;
+
+ const svalue *socket_type_sval = cd.get_arg_svalue (1);
+ state_machine::state_t new_state
+ = get_state_for_socket_type (socket_type_sval);
+ sm_ctxt->on_transition (node, stmt, new_fd, m_start, new_state);
+ model->set_value (cd.get_lhs_region (), new_fd, cd.get_ctxt ());
+ }
+ else
+ sm_ctxt->warn (node, stmt, NULL_TREE,
+ make_unique<fd_leak> (*this, NULL_TREE));
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ }
+
+ return true;
+}
+
+/* Check that FD_SVAL is usable by socket APIs.
+ Complain if it has been closed, if it is a non-socket,
+ or is invalid.
+ If COMPLAINED is non-NULL and a problem is found,
+ write *COMPLAINED = true.
+
+ If SUCCESSFUL is true, attempt to add the constraint that FD_SVAL >= 0.
+ Return true if this outcome is feasible. */
+
+bool
+fd_state_machine::check_for_socket_fd (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const svalue *fd_sval,
+ const supernode *node,
+ state_t old_state,
+ bool *complained) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+
+ if (is_closed_fd_p (old_state))
+ {
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_use_after_close> (*this, diag_arg,
+ cd.get_fndecl_for_call ()));
+ if (complained)
+ *complained = true;
+ if (successful)
+ return false;
+ }
+ else if (is_unchecked_fd_p (old_state) || is_valid_fd_p (old_state))
+ {
+ /* Complain about non-socket. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_type_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_TYPE_SOCKET));
+ if (complained)
+ *complained = true;
+ if (successful)
+ return false;
+ }
+ else if (old_state == m_invalid)
+ {
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_use_without_check> (*this, diag_arg,
+ cd.get_fndecl_for_call ()));
+ if (complained)
+ *complained = true;
+ if (successful)
+ return false;
+ }
+
+ if (successful)
+ if (!add_constraint_ge_zero (cd.get_model (), fd_sval, cd.get_ctxt ()))
+ return false;
+
+ return true;
+}
+
+/* For use by "bind" and "connect".
+ As per fd_state_machine::check_for_socket_fd above,
+ but also complain if we don't have a new socket, and check that
+ we can read up to the size bytes from the address. */
+
+bool
+fd_state_machine::check_for_new_socket_fd (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const svalue *fd_sval,
+ const supernode *node,
+ state_t old_state,
+ enum expected_phase expected_phase)
+ const
+{
+ bool complained = false;
+
+ /* Check address and len. */
+ const svalue *address_sval = cd.get_arg_svalue (1);
+ const svalue *len_sval = cd.get_arg_svalue (2);
+
+ /* Check that we can read the given number of bytes from the
+ address. */
+ region_model *model = cd.get_model ();
+ const region *address_reg
+ = model->deref_rvalue (address_sval, cd.get_arg_tree (1),
+ cd.get_ctxt ());
+ const region *sized_address_reg
+ = model->get_manager ()->get_sized_region (address_reg,
+ NULL_TREE,
+ len_sval);
+ model->get_store_value (sized_address_reg, cd.get_ctxt ());
+
+ if (!check_for_socket_fd (cd, successful, sm_ctxt,
+ fd_sval, node, old_state, &complained))
+ return false;
+ else if (!complained
+ && !(old_state == m_new_stream_socket
+ || old_state == m_new_datagram_socket
+ || old_state == m_new_unknown_socket
+ || old_state == m_start
+ || old_state == m_stop))
+ {
+ /* Complain about "bind" or "connect" in wrong phase. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ sm_ctxt->warn
+ (node, cd.get_call_stmt (), fd_sval,
+ make_unique<fd_phase_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ expected_phase));
+ if (successful)
+ return false;
+ }
+ else if (!successful)
+ {
+ /* If we were in the start state, assume we had a new socket. */
+ if (old_state == m_start)
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval,
+ m_new_unknown_socket);
+ }
+
+ /* Passing NULL as the address will lead to failure. */
+ if (successful)
+ if (address_sval->all_zeroes_p ())
+ return false;
+
+ return true;
+}
+
+/* Update the model and fd state for an outcome of a call to "bind",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_bind (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (stmt);
+ const svalue *fd_sval = cd.get_arg_svalue (0);
+ region_model *model = cd.get_model ();
+ state_t old_state = sm_ctxt->get_state (stmt, fd_sval);
+
+ if (!check_for_new_socket_fd (cd, successful, sm_ctxt,
+ fd_sval, node, old_state,
+ EXPECTED_PHASE_CAN_BIND))
+ return false;
+
+ if (successful)
+ {
+ state_t next_state = NULL;
+ if (old_state == m_new_stream_socket)
+ next_state = m_bound_stream_socket;
+ else if (old_state == m_new_datagram_socket)
+ next_state = m_bound_datagram_socket;
+ else if (old_state == m_new_unknown_socket)
+ next_state = m_bound_unknown_socket;
+ else if (old_state == m_start)
+ next_state = m_bound_unknown_socket;
+ else if (old_state == m_stop)
+ next_state = m_stop;
+ else
+ gcc_unreachable ();
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval, next_state);
+ model->update_for_zero_return (cd, true);
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ }
+
+ return true;
+}
+
+/* Update the model and fd state for an outcome of a call to "listen",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_listen (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (cd.get_call_stmt ());
+ const svalue *fd_sval = cd.get_arg_svalue (0);
+ region_model *model = cd.get_model ();
+ state_t old_state = sm_ctxt->get_state (stmt, fd_sval);
+
+ /* We expect a stream socket that's had "bind" called on it. */
+ if (!check_for_socket_fd (cd, successful, sm_ctxt, fd_sval, node, old_state))
+ return false;
+ if (!(old_state == m_start
+ || old_state == m_stop
+ || old_state == m_bound_stream_socket
+ || old_state == m_bound_unknown_socket
+ /* Assume it's OK to call "listen" more than once. */
+ || old_state == m_listening_stream_socket))
+ {
+ /* Complain about fncall on wrong type or in wrong phase. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ if (is_stream_socket_fd_p (old_state))
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_phase_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_PHASE_CAN_LISTEN));
+ else
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_type_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_TYPE_STREAM_SOCKET));
+ if (successful)
+ return false;
+ }
+
+ if (successful)
+ {
+ model->update_for_zero_return (cd, true);
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval,
+ m_listening_stream_socket);
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ if (old_state == m_start)
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval,
+ m_bound_stream_socket);
+ }
+
+ return true;
+}
+
+/* Update the model and fd state for an outcome of a call to "accept",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_accept (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (stmt);
+ const svalue *fd_sval = cd.get_arg_svalue (0);
+ const svalue *address_sval = cd.get_arg_svalue (1);
+ const svalue *len_ptr_sval = cd.get_arg_svalue (2);
+ region_model *model = cd.get_model ();
+ state_t old_state = sm_ctxt->get_state (stmt, fd_sval);
+
+ if (!address_sval->all_zeroes_p ())
+ {
+ region_model_manager *mgr = model->get_manager ();
+
+ /* We might have a union of various pointer types, rather than a
+ pointer type; cast to (void *) before dereferencing. */
+ address_sval = mgr->get_or_create_cast (ptr_type_node, address_sval);
+
+ const region *address_reg
+ = model->deref_rvalue (address_sval, cd.get_arg_tree (1),
+ cd.get_ctxt ());
+ const region *len_reg
+ = model->deref_rvalue (len_ptr_sval, cd.get_arg_tree (2),
+ cd.get_ctxt ());
+ const svalue *old_len_sval
+ = model->get_store_value (len_reg, cd.get_ctxt ());
+ tree len_ptr = cd.get_arg_tree (2);
+ tree star_len_ptr = build2 (MEM_REF, TREE_TYPE (TREE_TYPE (len_ptr)),
+ len_ptr,
+ build_int_cst (TREE_TYPE (len_ptr), 0));
+ old_len_sval = model->check_for_poison (old_len_sval,
+ star_len_ptr,
+ cd.get_ctxt ());
+ if (successful)
+ {
+ conjured_purge p (model, cd.get_ctxt ());
+ const region *old_sized_address_reg
+ = mgr->get_sized_region (address_reg,
+ NULL_TREE,
+ old_len_sval);
+ const svalue *new_addr_sval
+ = mgr->get_or_create_conjured_svalue (NULL_TREE,
+ stmt,
+ old_sized_address_reg,
+ p);
+ model->set_value (old_sized_address_reg, new_addr_sval,
+ cd.get_ctxt ());
+ const svalue *new_addr_len
+ = mgr->get_or_create_conjured_svalue (NULL_TREE,
+ stmt,
+ len_reg,
+ p);
+ model->set_value (len_reg, new_addr_len, cd.get_ctxt ());
+ }
+ }
+
+ /* We expect a stream socket in the "listening" state. */
+ if (!check_for_socket_fd (cd, successful, sm_ctxt, fd_sval, node, old_state))
+ return false;
+
+ if (old_state == m_start)
+ /* If we were in the start state, assume we had the expected state. */
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval,
+ m_listening_stream_socket);
+ else if (old_state == m_stop)
+ {
+ /* No further complaints. */
+ }
+ else if (old_state != m_listening_stream_socket)
+ {
+ /* Complain about fncall on wrong type or in wrong phase. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (fd_sval);
+ if (is_stream_socket_fd_p (old_state))
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_phase_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_PHASE_CAN_ACCEPT));
+ else
+ sm_ctxt->warn
+ (node, stmt, fd_sval,
+ make_unique<fd_type_mismatch> (*this, diag_arg,
+ cd.get_fndecl_for_call (),
+ old_state,
+ EXPECTED_TYPE_STREAM_SOCKET));
+ if (successful)
+ return false;
+ }
+
+ if (successful)
+ {
+ /* Return new conjured FD in "connected" state. */
+ if (gimple_call_lhs (stmt))
+ {
+ conjured_purge p (model, cd.get_ctxt ());
+ region_model_manager *mgr = model->get_manager ();
+ const svalue *new_fd
+ = mgr->get_or_create_conjured_svalue (integer_type_node,
+ stmt,
+ cd.get_lhs_region (),
+ p);
+ if (!add_constraint_ge_zero (model, new_fd, cd.get_ctxt ()))
+ return false;
+ sm_ctxt->on_transition (node, stmt, new_fd,
+ m_start, m_connected_stream_socket);
+ model->set_value (cd.get_lhs_region (), new_fd, cd.get_ctxt ());
+ }
+ else
+ sm_ctxt->warn (node, stmt, NULL_TREE,
+ make_unique<fd_leak> (*this, NULL_TREE));
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ }
+
+ return true;
+}
+
+/* Update the model and fd state for an outcome of a call to "connect",
+ where SUCCESSFUL indicate which of the two outcomes.
+ Return true if the outcome is feasible, or false to reject it. */
+
+bool
+fd_state_machine::on_connect (const call_details &cd,
+ bool successful,
+ sm_context *sm_ctxt,
+ const extrinsic_state &ext_state) const
+{
+ const gcall *stmt = cd.get_call_stmt ();
+ engine *eng = ext_state.get_engine ();
+ const supergraph *sg = eng->get_supergraph ();
+ const supernode *node = sg->get_supernode_for_stmt (stmt);
+ const svalue *fd_sval = cd.get_arg_svalue (0);
+ region_model *model = cd.get_model ();
+ state_t old_state = sm_ctxt->get_state (stmt, fd_sval);
+
+ if (!check_for_new_socket_fd (cd, successful, sm_ctxt,
+ fd_sval, node, old_state,
+ EXPECTED_PHASE_CAN_CONNECT))
+ return false;
+
+ if (successful)
+ {
+ model->update_for_zero_return (cd, true);
+ state_t next_state = NULL;
+ if (old_state == m_new_stream_socket)
+ next_state = m_connected_stream_socket;
+ else if (old_state == m_new_datagram_socket)
+ /* It's legal to call connect on a datagram socket, potentially
+ more than once. We don't transition states for this. */
+ next_state = m_new_datagram_socket;
+ else if (old_state == m_new_unknown_socket)
+ next_state = m_stop;
+ else if (old_state == m_start)
+ next_state = m_stop;
+ else if (old_state == m_stop)
+ next_state = m_stop;
+ else
+ gcc_unreachable ();
+ sm_ctxt->set_next_state (cd.get_call_stmt (), fd_sval, next_state);
+ }
+ else
+ {
+ /* Return -1; set errno. */
+ model->update_for_int_cst_return (cd, -1, true);
+ model->set_errno (cd);
+ /* TODO: perhaps transition to a failed state, since the
+ portable way to handle a failed "connect" is to close
+ the socket and try again with a new socket. */
+ }
+
+ return true;
+}
+
void
fd_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node,
const gimple *stmt, const svalue *lhs,
@@ -1215,7 +2191,9 @@ fd_state_machine::make_invalid_transitions_on_condition (
bool
fd_state_machine::can_purge_p (state_t s) const
{
- if (is_unchecked_fd_p (s) || is_valid_fd_p (s))
+ if (is_unchecked_fd_p (s)
+ || is_valid_fd_p (s)
+ || is_socket_fd_p (s))
return false;
else
return true;
@@ -1234,30 +2212,130 @@ make_fd_state_machine (logger *logger)
return new fd_state_machine (logger);
}
+static bool
+get_fd_state (region_model_context *ctxt,
+ sm_state_map **out_smap,
+ const fd_state_machine **out_sm,
+ unsigned *out_sm_idx,
+ std::unique_ptr<sm_context> *out_sm_context)
+{
+ if (!ctxt)
+ return false;
+
+ const state_machine *sm;
+ if (!ctxt->get_fd_map (out_smap, &sm, out_sm_idx, out_sm_context))
+ return false;
+
+ gcc_assert (sm);
+
+ *out_sm = (const fd_state_machine *)sm;
+ return true;
+}
+
/* Specialcase hook for handling pipe, for use by
region_model::impl_call_pipe::success::update_model. */
void
region_model::mark_as_valid_fd (const svalue *sval, region_model_context *ctxt)
{
- if (!ctxt)
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ if (!get_fd_state (ctxt, &smap, &fd_sm, NULL, NULL))
return;
const extrinsic_state *ext_state = ctxt->get_ext_state ();
if (!ext_state)
return;
+ fd_sm->mark_as_valid_fd (this, smap, sval, *ext_state);
+}
+/* Specialcase hook for handling "socket", for use by
+ region_model::impl_call_socket::outcome_of_socket::update_model. */
+
+bool
+region_model::on_socket (const call_details &cd, bool successful)
+{
sm_state_map *smap;
- const state_machine *sm;
- unsigned sm_idx;
- if (!ctxt->get_fd_map (&smap, &sm, &sm_idx))
- return;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
- gcc_assert (smap);
- gcc_assert (sm);
+ return fd_sm->on_socket (cd, successful, sm_ctxt.get (), *ext_state);
+}
+
+/* Specialcase hook for handling "bind", for use by
+ region_model::impl_call_bind::outcome_of_bind::update_model. */
+
+bool
+region_model::on_bind (const call_details &cd, bool successful)
+{
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
+
+ return fd_sm->on_bind (cd, successful, sm_ctxt.get (), *ext_state);
+}
+
+/* Specialcase hook for handling "listen", for use by
+ region_model::impl_call_listen::outcome_of_listen::update_model. */
+
+bool
+region_model::on_listen (const call_details &cd, bool successful)
+{
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
+
+ return fd_sm->on_listen (cd, successful, sm_ctxt.get (), *ext_state);
+}
+
+/* Specialcase hook for handling "accept", for use by
+ region_model::impl_call_accept::outcome_of_accept::update_model. */
+
+bool
+region_model::on_accept (const call_details &cd, bool successful)
+{
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
+
+ return fd_sm->on_accept (cd, successful, sm_ctxt.get (), *ext_state);
+}
- const fd_state_machine &fd_sm = (const fd_state_machine &)*sm;
+/* Specialcase hook for handling "connect", for use by
+ region_model::impl_call_connect::outcome_of_connect::update_model. */
+
+bool
+region_model::on_connect (const call_details &cd, bool successful)
+{
+ sm_state_map *smap;
+ const fd_state_machine *fd_sm;
+ std::unique_ptr<sm_context> sm_ctxt;
+ if (!get_fd_state (cd.get_ctxt (), &smap, &fd_sm, NULL, &sm_ctxt))
+ return true;
+ const extrinsic_state *ext_state = cd.get_ctxt ()->get_ext_state ();
+ if (!ext_state)
+ return true;
- fd_sm.mark_as_valid_fd (this, smap, sval, *ext_state);
+ return fd_sm->on_connect (cd, successful, sm_ctxt.get (), *ext_state);
}
} // namespace ana
@@ -46,6 +46,29 @@ digraph "fd" {
/* State for a file descriptor that has been closed. */
closed;
+ /* States for FDs relating to socket APIs. */
+
+ /* Result of successful "socket" with SOCK_DGRAM. */
+ new_datagram_socket;
+ /* Result of successful "socket" with SOCK_STREAM. */
+ new_stream_socket;
+ /* Result of successful "socket" with unknown type. */
+ new_unknown_socket;
+
+ /* The above after a successful call to "bind". */
+ bound_datagram_socket;
+ bound_stream_socket;
+ bound_unknown_socket;
+
+ /* A bound socket after a successful call to "listen" (stream or unknown). */
+ listening_stream_socket;
+
+ /* (i) the new FD as a result of a succesful call to "accept" on a
+ listening socket (via a passive open), or
+ (ii) an active socket after a successful call to "connect"
+ (via an active open). */
+ connected_stream_socket;
+
/* State for a file descriptor that we do not want to track anymore . */
stop;
@@ -68,6 +91,14 @@ digraph "fd" {
valid_read_only -> closed [label="on 'close(X);'"];
valid_write_only -> closed [label="on 'close(X);'"];
constant_fd -> closed [label="on 'close(X);'"];
+ new_datagram_socket -> closed [label="on 'close(X);'"];
+ new_stream_socket -> closed [label="on 'close(X);'"];
+ new_unknown_socket -> closed [label="on 'close(X);'"];
+ bound_datagram_socket -> closed [label="on 'close(X);'"];
+ bound_stream_socket -> closed [label="on 'close(X);'"];
+ bound_unknown_socket -> closed [label="on 'close(X);'"];
+ listening_stream_socket -> closed [label="on 'close(X);'"];
+ connected_stream_socket -> closed [label="on 'close(X);'"];
closed -> stop [label="on 'close(X);':\nWarn('double close')"];
/* On "read". */
@@ -91,6 +122,31 @@ digraph "fd" {
/* On "pipe". */
start -> valid_read_write [label="when 'pipe()' succeeds"];
+ /* On "socket". */
+ start -> new_datagram_socket [label="when 'socket(..., SOCK_DGRAM, ...)' succeeds"];
+ start -> new_stream_socket [label="when 'socket(..., SOCK_STREAM, ...)' succeeds"];
+ start -> new_unknown_socket [label="when 'socket(..., ..., ...)' succeeds"];
+
+ /* On "bind". */
+ start -> bound_unknown_socket [label="when 'bind(X, ...)' succeeds"];
+ new_stream_socket -> bound_stream_socket [label="when 'bind(X, ...)' succeeds"];
+ new_datagram_socket -> bound_datagram_socket [label="when 'bind(X, ...)' succeeds"];
+ new_unknown_socket -> bound_unknown_socket [label="when 'bind(X, ...)' succeeds"];
+
+ /* On "listen". */
+ start -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];
+ bound_stream_socket -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];
+ bound_unknown_socket -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];
+
+ /* On "accept". */
+ start -> connected_stream_socket [label="when 'accept(OTHER, ...)' succeeds on a listening_stream_socket"];
+
+ /* On "connect". */
+ new_stream_socket -> connected_stream_socket [label="when 'connect(X, ...)' succeeds"];
+ new_datagram_socket -> new_datagram_socket [label="when 'connect(X, ...)' succeeds"];
+ new_unknown_socket -> stop [label="when 'connect(X, ...)' succeeds"];
+ start -> stop [label="when 'connect(X, ...)' succeeds"];
+
/* on_condition. */
unchecked_read_write -> valid_read_write [label="on 'X >= 0'"];
unchecked_read_only -> valid_read_only [label="on 'X >= 0'"];
@@ -106,4 +162,12 @@ digraph "fd" {
valid_read_write -> stop [label="on leak:\nWarn('leak')"];
valid_read_only -> stop [label="on leak:\nWarn('leak')"];
valid_write_only -> stop [label="on leak:\nWarn('leak')"];
+ new_datagram_socket -> stop [label="on leak:\nWarn('leak')"];
+ new_stream_socket -> stop [label="on leak:\nWarn('leak')"];
+ new_unknown_socket -> stop [label="on leak:\nWarn('leak')"];
+ bound_datagram_socket -> stop [label="on leak:\nWarn('leak')"];
+ bound_stream_socket -> stop [label="on leak:\nWarn('leak')"];
+ bound_unknown_socket -> stop [label="on leak:\nWarn('leak')"];
+ listening_stream_socket -> stop [label="on leak:\nWarn('leak')"];
+ connected_stream_socket -> stop [label="on leak:\nWarn('leak')"];
}
@@ -453,6 +453,8 @@ Objective-C and Objective-C++ Dialects}.
-Wno-analyzer-fd-access-mode-mismatch @gol
-Wno-analyzer-fd-double-close @gol
-Wno-analyzer-fd-leak @gol
+-Wno-analyzer-fd-phase-mismatch @gol
+-Wno-analyzer-fd-type-mismatch @gol
-Wno-analyzer-fd-use-after-close @gol
-Wno-analyzer-fd-use-without-check @gol
-Wno-analyzer-file-leak @gol
@@ -9899,6 +9901,8 @@ Enabling this option effectively enables the following warnings:
-Wanalyzer-fd-access-mode-mismatch @gol
-Wanalyzer-fd-double-close @gol
-Wanalyzer-fd-leak @gol
+-Wanalyzer-fd-phase-mismatch @gol
+-Wanalyzer-fd-type-mismatch @gol
-Wanalyzer-fd-use-after-close @gol
-Wanalyzer-fd-use-without-check @gol
-Wanalyzer-file-leak @gol
@@ -10052,6 +10056,33 @@ open file descriptor is leaked.
See @uref{https://cwe.mitre.org/data/definitions/775.html, CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime}.
+@item -Wno-analyzer-fd-phase-mismatch
+@opindex Wanalyzer-fd-phase-mismatch
+@opindex Wno-analyzer-fd-phase-mismatch
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-phase-mismatch}
+to disable it.
+
+This diagnostic warns for paths through code in which an operation is
+attempted in the wrong phase of a file descriptor's lifetime.
+For example, it will warn on attempts to call @code{accept} on a stream
+socket that has not yet had @code{listen} successfully called on it.
+
+See @uref{https://cwe.mitre.org/data/definitions/666.html, CWE-666: Operation on Resource in Wrong Phase of Lifetime}.
+
+@item -Wno-analyzer-fd-type-mismatch
+@opindex Wanalyzer-fd-type-mismatch
+@opindex Wno-analyzer-fd-type-mismatch
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-type-mismatch}
+to disable it.
+
+This diagnostic warns for paths through code in which an
+operation is attempted on the wrong type of file descriptor.
+For example, it will warn on attempts to use socket operations
+on a file descriptor obtained via @code{open}, or when attempting
+to use a stream socket operation on a datagram socket.
+
@item -Wno-analyzer-fd-use-after-close
@opindex Wanalyzer-fd-use-after-close
@opindex Wno-analyzer-fd-use-after-close
@@ -10504,6 +10535,7 @@ of the following functions for working with file descriptors:
@item @code{pipe}, and @code{pipe2}
@item @code{read}
@item @code{write}
+@item @code{socket}, @code{bind}, @code{listen}, @code{accept}, and @code{connect}
@end itemize
of the following functions for working with @code{<stdio.h>} streams:
new file mode 100644
@@ -0,0 +1,69 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+int test_accept (int fd, struct sockaddr *addr, socklen_t *addrlen)
+{
+ return accept (fd, addr, addrlen);
+}
+
+void test_accept_leak_no_lhs (int fd, struct sockaddr *addr, socklen_t *addrlen)
+{
+ accept (fd, addr, addrlen); /* { dg-warning "leak of file descriptor" } */
+}
+
+void test_accept_leak_with_lhs (int fd, struct sockaddr *addr, socklen_t *addrlen)
+{
+ int newfd = accept (fd, addr, addrlen); /* { dg-message "socket created here" } */
+} /* { dg-warning "leak of file descriptor 'newfd'" } */
+
+int test_accept_null_addr (int fd)
+{
+ return accept (fd, NULL, 0);
+}
+
+int test_accept_uninit_addrlen (int fd)
+{
+ struct sockaddr_storage addr;
+ socklen_t addr_len;
+ return accept (fd, (struct sockaddr *)&addr, &addr_len); /* { dg-warning "use of uninitialized value 'addr_len'" } */
+}
+
+int test_accept_writes_to_addr_and_len (int fd)
+{
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof (addr);
+ __analyzer_eval (addr_len == sizeof (addr)); /* { dg-warning "TRUE" } */
+ int newfd = accept (fd, (struct sockaddr *)&addr, &addr_len);
+ if (newfd == -1)
+ return newfd;
+ /* Check that the analyzer considers addr and addr_len to
+ have been written to. */
+ __analyzer_eval (((char *)&addr)[0]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (addr_len == sizeof (addr)); /* { dg-warning "UNKNOWN" } */
+ return newfd;
+}
+
+void test_accept_on_new_datagram_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ return;
+ accept (fd, NULL, NULL); /* { dg-message "'accept' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'accept' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+int test_accept_on_accept (int fd_a)
+{
+ int fd_b = accept (fd_a, NULL, 0);
+ if (fd_b == -1)
+ return -1;
+
+ int fd_c = accept (fd_b, NULL, 0); /* { dg-warning "'accept' on file descriptor 'fd_b' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+ /* { dg-message "'accept' expects a listening stream socket file descriptor but 'fd_b' is connected" "final event" { target *-*-* } .-1 } */
+
+ return fd_b;
+}
new file mode 100644
@@ -0,0 +1,74 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_bind (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ __analyzer_dump_path (); /* { dg-message "path" } */
+ else
+ __analyzer_dump_path (); /* { dg-message "path" } */
+}
+
+void test_null_bind (int fd)
+{
+ errno = 0;
+ int result = bind (fd, NULL, 0);
+ __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+}
+
+void test_double_bind (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+ /* { dg-message "'bind' expects a new socket file descriptor but 'fd' has already been bound" "final event" { target *-*-* } .-1 } */
+}
+
+int test_uninit_addr (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ return bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ // TODO: complain about uninit addr.
+}
+
+void test_bind_after_connect (int fd, const char *sockname,
+ const struct sockaddr *caddr, socklen_t caddrlen)
+{
+ if (connect (fd, caddr, caddrlen) == -1)
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ /* TODO: we don't warn for this; after the plain "connect" we're
+ in the stop state. */
+}
+
+void test_bind_after_accept (int fd, const char *sockname)
+{
+ int afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (afd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'afd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+ /* { dg-message "'bind' expects a new socket file descriptor but 'afd' is already connected" "final event" { target *-*-* } .-1 } */
+
+ close (afd);
+}
new file mode 100644
@@ -0,0 +1,46 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+int test_connect (int sockfd, const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ return connect (sockfd, addr, addrlen);
+}
+
+void test_null_connect (int fd)
+{
+ errno = 0;
+ int result = connect (fd, NULL, 0);
+ __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+}
+
+int test_uninit_addr (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ return connect (fd, (struct sockaddr *)&addr, sizeof (addr));
+ // TODO: complain about uninit addr.
+}
+
+void test_connect_after_bind (const char *sockname,
+ const struct sockaddr *baddr, socklen_t baddrlen,
+ const struct sockaddr *caddr, socklen_t caddrlen)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+
+ if (bind (fd, baddr, baddrlen) == -1)
+ {
+ close (fd);
+ return;
+ }
+
+ connect (fd, caddr, caddrlen); /* { dg-warning "'connect' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'connect' expects a new socket file descriptor but 'fd' is bound" "final event" { target *-*-* } .-1 } */
+
+ close (fd);
+}
new file mode 100644
@@ -0,0 +1,108 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_leak_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_socket_no_lhs (void)
+{
+ socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-warning "leak of file descriptor" } */
+}
+
+void test_close_unchecked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ close (fd);
+}
+
+void test_close_checked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ return;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */
+ close (fd);
+}
+
+void test_leak_checked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+ if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ return;
+ // TODO: strange location for leak message
+}
+
+void test_bind (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ return;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ close (fd);
+}
+
+void test_bind_on_unchecked_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "when 'socket' fails" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */
+ close (fd);
+}
+
+void test_leak_of_bound_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
+}
+
+void test_listen_on_datagram_socket_without_bind (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+ if (fd == -1)
+ return;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */
+ listen (fd, 5); /* { dg-warning "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_on_datagram_socket_with_bind (const char *sockname)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */
+ if (fd == -1)
+ return;
+
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) /* { dg message "datagram socket bound here" } */
+ {
+ close (fd);
+ return;
+ }
+ listen (fd, 5); /* { dg-warning "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
new file mode 100644
@@ -0,0 +1,133 @@
+/* Example from glibc manual (16.9.7). */
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#define PORT 5555
+#define MAXMSG 512
+
+int
+make_socket (uint16_t port)
+{
+ int sock;
+ struct sockaddr_in name;
+
+ /* Create the socket. */
+ sock = socket (PF_INET, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ perror ("socket");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Give the socket a name. */
+ name.sin_family = AF_INET;
+ name.sin_port = htons (port);
+ name.sin_addr.s_addr = htonl (INADDR_ANY);
+ if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
+ {
+ perror ("bind");
+ exit (EXIT_FAILURE);
+ }
+
+ return sock;
+}
+
+int
+read_from_client (int filedes)
+{
+ char buffer[MAXMSG];
+ int nbytes;
+
+ nbytes = read (filedes, buffer, MAXMSG);
+ if (nbytes < 0)
+ {
+ /* Read error. */
+ perror ("read");
+ exit (EXIT_FAILURE);
+ }
+ else if (nbytes == 0)
+ /* End-of-file. */
+ return -1;
+ else
+ {
+ /* Data read. */
+ fprintf (stderr, "Server: got message: `%s'\n", buffer);
+ return 0;
+ }
+}
+
+int
+main (void)
+{
+ int sock;
+ fd_set active_fd_set, read_fd_set;
+ int i;
+ struct sockaddr_in clientname;
+ socklen_t size;
+
+ /* Create the socket and set it up to accept connections. */
+ sock = make_socket (PORT);
+ if (listen (sock, 1) < 0)
+ {
+ perror ("listen");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Initialize the set of active sockets. */
+ FD_ZERO (&active_fd_set);
+ FD_SET (sock, &active_fd_set);
+
+ while (1)
+ {
+ /* Block until input arrives on one or more active sockets. */
+ read_fd_set = active_fd_set;
+ if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0)
+ {
+ perror ("select");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Service all the sockets with input pending. */
+ for (i = 0; i < FD_SETSIZE; ++i)
+ if (FD_ISSET (i, &read_fd_set))
+ {
+ if (i == sock)
+ {
+ /* Connection request on original socket. */
+ int new;
+ size = sizeof (clientname);
+ new = accept (sock,
+ (struct sockaddr *) &clientname,
+ &size);
+ if (new < 0)
+ {
+ perror ("accept");
+ exit (EXIT_FAILURE);
+ }
+ fprintf (stderr,
+ "Server: connect from host %s, port %hd.\n",
+ inet_ntoa (clientname.sin_addr),
+ ntohs (clientname.sin_port));
+ FD_SET (new, &active_fd_set);
+ }
+ else
+ {
+ /* Data arriving on an already-connected socket. */
+ if (read_from_client (i) < 0)
+ {
+ close (i);
+ FD_CLR (i, &active_fd_set);
+ }
+ }
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,62 @@
+/* Example from glibc manual (16.9.6). */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#define PORT 5555
+#define MESSAGE "Yow!!! Are we having fun yet?!?"
+#define SERVERHOST "www.gnu.org"
+
+void
+write_to_server (int filedes)
+{
+ int nbytes;
+
+ nbytes = write (filedes, MESSAGE, strlen (MESSAGE) + 1);
+ if (nbytes < 0)
+ {
+ perror ("write");
+ exit (EXIT_FAILURE);
+ }
+}
+
+
+int
+main (void)
+{
+ extern void init_sockaddr (struct sockaddr_in *name,
+ const char *hostname,
+ uint16_t port);
+ int sock;
+ struct sockaddr_in servername;
+
+ /* Create the socket. */
+ sock = socket (PF_INET, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ perror ("socket (client)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Connect to the server. */
+ init_sockaddr (&servername, SERVERHOST, PORT);
+ if (0 > connect (sock,
+ (struct sockaddr *) &servername,
+ sizeof (servername)))
+ {
+ perror ("connect (client)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Send data to the server. */
+ write_to_server (sock);
+ close (sock);
+ exit (EXIT_SUCCESS);
+}
new file mode 100644
@@ -0,0 +1,56 @@
+/* Example from the glibc manual (16.10.4). */
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "fd-glibc-make_named_socket.h"
+
+#define SERVER "/tmp/serversocket"
+#define CLIENT "/tmp/mysocket"
+#define MAXMSG 512
+#define MESSAGE "Yow!!! Are we having fun yet?!?"
+
+int
+main (void)
+{
+ int sock;
+ char message[MAXMSG];
+ struct sockaddr_un name;
+ size_t size;
+ int nbytes;
+
+ /* Make the socket. */
+ sock = make_named_socket (CLIENT);
+
+ /* Initialize the server socket address. */
+ name.sun_family = AF_LOCAL;
+ strcpy (name.sun_path, SERVER);
+ size = strlen (name.sun_path) + sizeof (name.sun_family);
+
+ /* Send the datagram. */
+ nbytes = sendto (sock, MESSAGE, strlen (MESSAGE) + 1, 0,
+ (struct sockaddr *) & name, size);
+ if (nbytes < 0)
+ {
+ perror ("sendto (client)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Wait for a reply. */
+ nbytes = recvfrom (sock, message, MAXMSG, 0, NULL, 0);
+ if (nbytes < 0)
+ {
+ perror ("recfrom (client)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Print a diagnostic message. */
+ fprintf (stderr, "Client: got message: %s\n", message);
+
+ /* Clean up. */
+ remove (CLIENT);
+ close (sock);
+}
new file mode 100644
@@ -0,0 +1,52 @@
+/* Example from glibc manual (16.10.3). */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include "fd-glibc-make_named_socket.h"
+
+#define SERVER "/tmp/serversocket"
+#define MAXMSG 512
+
+int
+main (void)
+{
+ int sock;
+ char message[MAXMSG];
+ struct sockaddr_un name;
+ socklen_t size;
+ int nbytes;
+
+ /* Remove the filename first, it’s ok if the call fails */
+ unlink (SERVER);
+
+ /* Make the socket, then loop endlessly. */
+ sock = make_named_socket (SERVER);
+ while (1)
+ {
+ /* Wait for a datagram. */
+ size = sizeof (name);
+ nbytes = recvfrom (sock, message, MAXMSG, 0,
+ (struct sockaddr *) & name, &size);
+ if (nbytes < 0)
+ {
+ perror ("recfrom (server)");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Give a diagnostic message. */
+ fprintf (stderr, "Server: got message: %s\n", message);
+
+ /* Bounce the message back to the sender. */
+ nbytes = sendto (sock, message, nbytes, 0,
+ (struct sockaddr *) & name, size);
+ if (nbytes < 0)
+ {
+ perror ("sendto (server)");
+ exit (EXIT_FAILURE);
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,47 @@
+/* Example of Local-Namespace Sockets from the glibc manual (16.5.3). */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+int
+make_named_socket (const char *filename)
+{
+ struct sockaddr_un name;
+ int sock;
+ size_t size;
+
+ /* Create the socket. */
+ sock = socket (PF_LOCAL, SOCK_DGRAM, 0);
+ if (sock < 0)
+ {
+ perror ("socket");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Bind a name to the socket. */
+ name.sun_family = AF_LOCAL;
+ strncpy (name.sun_path, filename, sizeof (name.sun_path));
+ name.sun_path[sizeof (name.sun_path) - 1] = '\0';
+
+ /* The size of the address is
+ the offset of the start of the filename,
+ plus its length (not including the terminating null byte).
+ Alternatively you can just do:
+ size = SUN_LEN (&name);
+ */
+ size = (offsetof (struct sockaddr_un, sun_path)
+ + strlen (name.sun_path));
+
+ if (bind (sock, (struct sockaddr *) &name, size) < 0)
+ {
+ perror ("bind");
+ exit (EXIT_FAILURE);
+ }
+
+ return sock;
+}
new file mode 100644
@@ -0,0 +1,63 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+int test_listen (int fd, int backlog)
+{
+ return listen (fd, backlog);
+}
+
+/* Some systems seem to allow repeated calls to listen. */
+
+void test_double_listen (int fd, int backlog)
+{
+ listen (fd, backlog);
+ listen (fd, backlog);
+}
+
+void test_listen_before_bind (int fd, const char *sockname)
+{
+ if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'bind' expects a new socket file descriptor but 'fd' is already listening" "final event" { target *-*-* } .-1 } */
+}
+
+void test_listen_on_unchecked_bind (int fd, const char *sockname)
+{
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_on_new_datagram_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ return;
+ listen (fd, 5); /* { dg-message "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listed_on_connected_socket (int fd)
+{
+ int afd = accept (fd, NULL, 0);
+ if (afd == -1)
+ return;
+ listen (afd, 5); /* { dg-warning "'listen' on file descriptor 'afd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'afd' is connected" "final event" { target *-*-* } .-1 } */
+ close (afd);
+}
new file mode 100644
@@ -0,0 +1,122 @@
+/* Example from getaddrinfo.3 manpage, which has this license:
+
+Copyright (c) 2007, 2008 Michael Kerrisk <mtk.manpages@gmail.com>
+and Copyright (c) 2006 Ulrich Drepper <drepper@redhat.com>
+A few pieces of an earlier version remain:
+Copyright 2000, Sam Varshavchik <mrsam@courier-mta.com>
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+
+Since the Linux kernel and libraries are constantly changing, this
+manual page may be incorrect or out-of-date. The author(s) assume no
+responsibility for errors or omissions, or for damages resulting from
+the use of the information contained herein. The author(s) may not
+have taken the same level of care in the production of this manual,
+which is licensed free of charge, as they might when working
+professionally.
+
+Formatted or processed versions of this manual, if unaccompanied by
+the source, must acknowledge the copyright and authors of this work.
+*/
+
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define BUF_SIZE 500
+
+int
+main(int argc, char *argv[])
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sfd, s;
+ size_t len;
+ ssize_t nread;
+ char buf[BUF_SIZE];
+
+ if (argc < 3) {
+ fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Obtain address(es) matching host/port. */
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0; /* Any protocol */
+
+ s = getaddrinfo(argv[1], argv[2], &hints, &result);
+ if (s != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
+ exit(EXIT_FAILURE);
+ }
+
+ /* getaddrinfo() returns a list of address structures.
+ Try each address until we successfully connect(2).
+ If socket(2) (or connect(2)) fails, we (close the socket
+ and) try the next address. */
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sfd = socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (sfd == -1)
+ continue;
+
+ if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
+ break; /* Success */
+
+ close(sfd);
+ }
+
+ freeaddrinfo(result); /* No longer needed */
+
+ if (rp == NULL) { /* No address succeeded */
+ fprintf(stderr, "Could not connect\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Send remaining command-line arguments as separate
+ datagrams, and read responses from server. */
+
+ for (int j = 3; j < argc; j++) {
+ len = strlen(argv[j]) + 1;
+ /* +1 for terminating null byte */
+
+ if (len > BUF_SIZE) {
+ fprintf(stderr,
+ "Ignoring long message in argument %d\n", j);
+ continue;
+ }
+
+ if (write(sfd, argv[j], len) != len) {
+ fprintf(stderr, "partial/failed write\n");
+ exit(EXIT_FAILURE);
+ }
+
+ nread = read(sfd, buf, BUF_SIZE);
+ if (nread == -1) {
+ perror("read");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("Received %zd bytes: %s\n", nread, buf);
+ }
+
+ exit(EXIT_SUCCESS);
+}
new file mode 100644
@@ -0,0 +1,119 @@
+/* Example from getaddrinfo.3 manpage, which has this license:
+
+Copyright (c) 2007, 2008 Michael Kerrisk <mtk.manpages@gmail.com>
+and Copyright (c) 2006 Ulrich Drepper <drepper@redhat.com>
+A few pieces of an earlier version remain:
+Copyright 2000, Sam Varshavchik <mrsam@courier-mta.com>
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+
+Since the Linux kernel and libraries are constantly changing, this
+manual page may be incorrect or out-of-date. The author(s) assume no
+responsibility for errors or omissions, or for damages resulting from
+the use of the information contained herein. The author(s) may not
+have taken the same level of care in the production of this manual,
+which is licensed free of charge, as they might when working
+professionally.
+
+Formatted or processed versions of this manual, if unaccompanied by
+the source, must acknowledge the copyright and authors of this work.
+*/
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#define BUF_SIZE 500
+
+int
+main(int argc, char *argv[])
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sfd, s;
+ struct sockaddr_storage peer_addr;
+ socklen_t peer_addr_len;
+ ssize_t nread;
+ char buf[BUF_SIZE];
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s port\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
+ hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
+ hints.ai_protocol = 0; /* Any protocol */
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+
+ s = getaddrinfo(NULL, argv[1], &hints, &result);
+ if (s != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
+ exit(EXIT_FAILURE);
+ }
+
+ /* getaddrinfo() returns a list of address structures.
+ Try each address until we successfully bind(2).
+ If socket(2) (or bind(2)) fails, we (close the socket
+ and) try the next address. */
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sfd = socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (sfd == -1)
+ continue;
+
+ if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break; /* Success */
+
+ close(sfd);
+ }
+
+ freeaddrinfo(result); /* No longer needed */
+
+ if (rp == NULL) { /* No address succeeded */
+ fprintf(stderr, "Could not bind\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Read datagrams and echo them back to sender. */
+
+ for (;;) {
+ peer_addr_len = sizeof(peer_addr);
+ nread = recvfrom(sfd, buf, BUF_SIZE, 0,
+ (struct sockaddr *) &peer_addr, &peer_addr_len);
+ if (nread == -1)
+ continue; /* Ignore failed request */
+
+ char host[NI_MAXHOST], service[NI_MAXSERV];
+
+ s = getnameinfo((struct sockaddr *) &peer_addr,
+ peer_addr_len, host, NI_MAXHOST,
+ service, NI_MAXSERV, NI_NUMERICSERV);
+ if (s == 0)
+ printf("Received %zd bytes from %s:%s\n",
+ nread, host, service);
+ else
+ fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));
+
+ if (sendto(sfd, buf, nread, 0,
+ (struct sockaddr *) &peer_addr,
+ peer_addr_len) != nread)
+ fprintf(stderr, "Error sending response\n");
+ }
+}
new file mode 100644
@@ -0,0 +1,21 @@
+/* { dg-additional-options "-fanalyzer-verbose-state-changes" } */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+
+void test_leak_unchecked_stream_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_unchecked_datagram_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_unchecked_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
new file mode 100644
@@ -0,0 +1,98 @@
+/* Various operations done on sockets in the wrong phase. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_read_on_new_socket (void *buf)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+ read (fd, buf, 1); /* { dg-warning "'read' on file descriptor 'fd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+ /* { dg-message "'read' expects a stream socket to be connected via 'accept' but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_read_on_bound_socket (int fd, const char *sockname, void *buf)
+{
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ return;
+ /* This could be a datagram socket, so we shouldn't complain here. */
+ read (fd, buf, 1);
+}
+
+void test_read_on_listening_socket (int fd, void *buf)
+{
+ if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */
+ return;
+ read (fd, buf, 1); /* { dg-message "'read' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'read' expects a stream socket to be connected via the return value of 'accept' but 'fd' is listening; wrong file descriptor\\\?" "final event" { target *-*-* } .-1 } */
+}
+
+void test_bind_on_non_socket (const char *filename, const char *sockname)
+{
+ int fd = open (filename, O_RDONLY);
+ if (fd == -1)
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ int result = bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on non-socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */
+ /* { dg-message "'bind' expects a socket file descriptor but 'fd' is not a socket" "final event" { target *-*-* } .-1 } */
+ __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */
+
+ close (fd);
+}
+
+void test_passive_open_read_on_wrong_socket (int sfd)
+{
+ int cfd = accept (sfd, NULL, NULL);
+ write (sfd, "hello", 6); /* { dg-warning "'write' on file descriptor 'sfd' in wrong phase" "warning" } */
+ /* { dg-message "'write' expects a stream socket to be connected via the return value of 'accept' but 'sfd' is listening; wrong file descriptor\\\?" "final event" { target *-*-* } .-1 } */
+ close (cfd);
+}
+
+void test_listen_on_new_stream_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ return;
+ listen (fd, 5); /* { dg-message "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_accept_on_new_stream_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ return;
+ accept (fd, NULL, NULL); /* { dg-message "'accept' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'accept' expects a listening stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_before_bind (int fd, const char *sockname)
+{
+ if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */
+ return;
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'bind' expects a new socket file descriptor but 'fd' is already listening" "final event" { target *-*-* } .-1 } */
+}
new file mode 100644
@@ -0,0 +1,74 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_active_open_from_scratch (const char *sockname, void *buf)
+{
+ errno = 0;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-invalid'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+
+ errno = 0;
+ if (connect (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+
+ write (fd, "hello", 6);
+ read (fd, buf, 100);
+
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
+
+void test_active_open_from_connect (int fd, const char *sockname, void *buf)
+{
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */
+
+ struct sockaddr_un addr;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+
+ errno = 0;
+ if (connect (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-unknown-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-stop'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+
+ write (fd, "hello", 6);
+ read (fd, buf, 100);
+
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-stop'" } */
+}
new file mode 100644
@@ -0,0 +1,197 @@
+/* Verify the various states when performing a passive open,
+ either from scratch, or when various phases are assumed to already
+ be done. */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_passive_open_from_scratch (const char *sockname, void *buf)
+{
+ struct sockaddr_un addr;
+ int afd;
+ errno = 0;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-invalid'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ errno = 0;
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ if (listen (fd, 5) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */
+
+ write (afd, "hello", 6);
+ read (afd, buf, 100);
+
+ close (afd);
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
+
+void test_passive_open_from_bind (int fd, const char *sockname, void *buf)
+{
+ struct sockaddr_un addr;
+ int afd;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ errno = 0;
+ if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-unknown-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-unknown-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ if (listen (fd, 5) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-unknown-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */
+
+ write (afd, "hello", 6);
+ read (afd, buf, 100);
+
+ close (afd);
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
+
+void test_passive_open_from_listen (int fd, void *buf)
+{
+ int afd;
+ errno = 0;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */
+ if (listen (fd, 5) == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */
+
+ write (afd, "hello", 6);
+ read (afd, buf, 100);
+
+ close (afd);
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
+
+void test_passive_open_from_accept (int fd, void *buf)
+{
+ int afd;
+ errno = 0;
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */
+ afd = accept (fd, NULL, NULL);
+ if (afd == -1)
+ {
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+ return;
+ }
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */
+ __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */
+
+ write (afd, "hello", 6);
+ read (afd, buf, 100);
+
+ close (afd);
+ close (fd);
+ __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */
+ __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */
+}
new file mode 100644
@@ -0,0 +1,98 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_leak_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_socket_no_lhs (void)
+{
+ socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-warning "leak of file descriptor" } */
+}
+
+void test_close_unchecked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ close (fd);
+}
+
+void test_close_checked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ return;
+ close (fd);
+}
+
+void test_leak_checked_socket (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ return;
+ // TODO: strange location for leak message
+}
+
+void test_bind_on_checked_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ close (fd);
+}
+
+void test_bind_on_unchecked_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "when 'socket' fails" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */
+ close (fd);
+}
+
+void test_leak_of_bound_socket (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
+}
+
+void test_listen_without_bind (void)
+{
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_on_unchecked_bind (const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
+ close (fd);
+}
new file mode 100644
@@ -0,0 +1,98 @@
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "analyzer-decls.h"
+
+void test_leak_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */
+} /* { dg-warning "leak of file descriptor 'fd'" } */
+
+void test_leak_socket_no_lhs (int type)
+{
+ socket (AF_UNIX, type, 0); /* { dg-warning "leak of file descriptor" } */
+}
+
+void test_close_unchecked_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0);
+ close (fd);
+}
+
+void test_close_checked_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0);
+ if (fd == -1)
+ return;
+ close (fd);
+}
+
+void test_leak_checked_socket (int type)
+{
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */
+ if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */
+ return;
+ // TODO: strange location for leak message
+}
+
+void test_bind_on_checked_socket (int type, const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, type, 0);
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr));
+ close (fd);
+}
+
+void test_bind_on_unchecked_socket (int type, const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "when 'socket' fails" } */
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */
+ close (fd);
+}
+
+void test_leak_of_bound_socket (int type, const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */
+}
+
+void test_listen_without_bind (int type)
+{
+ int fd = socket (AF_UNIX, type, 0);
+ if (fd == -1)
+ return;
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "msg" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void test_listen_on_unchecked_bind (int type, const char *sockname)
+{
+ struct sockaddr_un addr;
+ int fd = socket (AF_UNIX, type, 0);
+ if (fd == -1)
+ return;
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
+ bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */
+ listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */
+ /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "msg" { target *-*-* } .-1 } */
+ close (fd);
+}
@@ -1,5 +1,5 @@
-/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=10" } */
-// TODO: remove need for this option
+/* { dg-additional-options "-Wno-analyzer-too-complex -Wno-analyzer-fd-leak" } */
+// TODO: remove need for these options
typedef __SIZE_TYPE__ size_t;
#define NULL ((void *)0)
@@ -1,3 +1,6 @@
+/* { dg-additional-options "-Wno-analyzer-fd-leak" } */
+// TODO: remove need for this option
+
typedef __SIZE_TYPE__ size_t;
#define NULL ((void *)0)
#define POLLIN 0x001
This patch generalizes the analyzer's file descriptor state machine so that it tracks the states of sockets. It adds two new warnings relating to misuses of socket APIs: * -Wanalyzer-fd-phase-mismatch (e.g. calling 'accept' on a socket before calling 'listen' on it) * -Wanalyzer-fd-type-mismatch (e.g. using a stream socket operation on a datagram socket) Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. This patch depends on the named constants patch here: [PATCH] c, analyzer: support named constants in analyzer [PR106302] https://gcc.gnu.org/pipermail/gcc-patches/2022-October/604739.html gcc/analyzer/ChangeLog: PR analyzer/106140 * analyzer-language.cc (on_finish_translation_unit): Stash named constants "SOCK_STREAM" and "SOCK_DGRAM". * analyzer.opt (Wanalyzer-fd-phase-mismatch): New. (Wanalyzer-fd-type-mismatch): New. * engine.cc (impl_region_model_context::get_state_map_by_name): Add "out_sm_context" param. Allow out_sm_idx to be NULL. * exploded-graph.h (impl_region_model_context::get_state_map_by_name): Add "out_sm_context" param. * region-model-impl-calls.cc (region_model::impl_call_accept): New. (region_model::impl_call_bind): New. (region_model::impl_call_connect): New. (region_model::impl_call_listen): New. (region_model::impl_call_socket): New. * region-model.cc (region_model::on_call_pre): Special-case "bind". (region_model::on_call_post): Special-case "accept", "bind", "connect", "listen", and "socket". * region-model.h (region_model::impl_call_accept): New decl. (region_model::impl_call_bind): New decl. (region_model::impl_call_connect): New decl. (region_model::impl_call_listen): New decl. (region_model::impl_call_socket): New decl. (region_model::on_socket): New decl. (region_model::on_bind): New decl. (region_model::on_listen): New decl. (region_model::on_accept): New decl. (region_model::on_connect): New decl. (region_model::add_constraint): Make public. (region_model::check_for_poison): Make public. (region_model_context::get_state_map_by_name): Add out_sm_context param. (region_model_context::get_fd_map): Likewise. (region_model_context::get_malloc_map): Likewise. (region_model_context::get_taint_map): Likewise. (noop_region_model_context::get_state_map_by_name): Likewise. (region_model_context_decorator::get_state_map_by_name): Likewise. * sm-fd.cc: Include "analyzer/supergraph.h" and "analyzer/analyzer-language.h". (enum expected_phase): New enum. (fd_state_machine::m_new_datagram_socket): New. (fd_state_machine::m_new_stream_socket): New. (fd_state_machine::m_new_unknown_socket): New. (fd_state_machine::m_bound_datagram_socket): New. (fd_state_machine::m_bound_stream_socket): New. (fd_state_machine::m_bound_unknown_socket): New. (fd_state_machine::m_listening_stream_socket): New. (fd_state_machine::m_m_connected_stream_socket): New. (fd_state_machine::m_SOCK_STREAM): New. (fd_state_machine::m_SOCK_DGRAM): New. (fd_diagnostic::describe_state_change): Handle socket states. (fd_diagnostic::get_meaning_for_state_change): Likewise. (class fd_phase_mismatch): New. (enum expected_type): New enum. (class fd_type_mismatch): New. (fd_state_machine::fd_state_machine): Initialize new states and stashed named constants. (fd_state_machine::is_socket_fd_p): New. (fd_state_machine::is_datagram_socket_fd_p): New. (fd_state_machine::is_stream_socket_fd_p): New. (fd_state_machine::on_close): Handle the socket states. (fd_state_machine::check_for_open_fd): Complain about fncalls on sockets in the wrong phase. Support socket FDs. (add_constraint_ge_zero): New. (fd_state_machine::get_state_for_socket_type): New. (fd_state_machine::on_socket): New. (fd_state_machine::check_for_socket_fd): New. (fd_state_machine::check_for_new_socket_fd): New. (fd_state_machine::on_bind): New. (fd_state_machine::on_listen): New. (fd_state_machine::on_accept): New. (fd_state_machine::on_connect): New. (fd_state_machine::can_purge_p): Don't purge socket values. (get_fd_state): New. (region_model::mark_as_valid_fd): Use get_fd_state. (region_model::on_socket): New. (region_model::on_bind): New. (region_model::on_listen): New. (region_model::on_accept): New. (region_model::on_connect): New. * sm-fd.dot: Update to reflect sm-fd.cc changes. gcc/ChangeLog: PR analyzer/106140 * doc/invoke.texi (Static Analyzer Options): Add -Wanalyzer-fd-phase-mismatch and -Wanalyzer-fd-type-mismatch. gcc/testsuite/ChangeLog: PR analyzer/106140 * gcc.dg/analyzer/fd-accept.c: New test. * gcc.dg/analyzer/fd-bind.c: New test. * gcc.dg/analyzer/fd-connect.c: New test. * gcc.dg/analyzer/fd-datagram-socket.c: New test. * gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c: New test. * gcc.dg/analyzer/fd-glibc-byte-stream-socket.c: New test. * gcc.dg/analyzer/fd-glibc-datagram-client.c: New test. * gcc.dg/analyzer/fd-glibc-datagram-socket.c: New test. * gcc.dg/analyzer/fd-glibc-make_named_socket.h: New test. * gcc.dg/analyzer/fd-listen.c: New test. * gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c: New test. * gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c: New test. * gcc.dg/analyzer/fd-socket-meaning.c: New test. * gcc.dg/analyzer/fd-socket-misuse.c: New test. * gcc.dg/analyzer/fd-stream-socket-active-open.c: New test. * gcc.dg/analyzer/fd-stream-socket-passive-open.c: New test. * gcc.dg/analyzer/fd-stream-socket.c: New test. * gcc.dg/analyzer/fd-symbolic-socket.c: New test. * gcc.dg/analyzer/pr104369-1.c: Add -Wno-analyzer-too-complex and -Wno-analyzer-fd-leak to options. * gcc.dg/analyzer/pr104369-2.c: Add -Wno-analyzer-fd-leak to options. Signed-off-by: David Malcolm <dmalcolm@redhat.com> --- gcc/analyzer/analyzer-language.cc | 2 + gcc/analyzer/analyzer.opt | 8 + gcc/analyzer/engine.cc | 60 +- gcc/analyzer/exploded-graph.h | 10 +- gcc/analyzer/region-model-impl-calls.cc | 150 +++ gcc/analyzer/region-model.cc | 30 + gcc/analyzer/region-model.h | 57 +- gcc/analyzer/sm-fd.cc | 1110 ++++++++++++++++- gcc/analyzer/sm-fd.dot | 64 + gcc/doc/invoke.texi | 32 + gcc/testsuite/gcc.dg/analyzer/fd-accept.c | 69 + gcc/testsuite/gcc.dg/analyzer/fd-bind.c | 74 ++ gcc/testsuite/gcc.dg/analyzer/fd-connect.c | 46 + .../gcc.dg/analyzer/fd-datagram-socket.c | 108 ++ .../fd-glibc-byte-stream-connection-server.c | 133 ++ .../analyzer/fd-glibc-byte-stream-socket.c | 62 + .../analyzer/fd-glibc-datagram-client.c | 56 + .../analyzer/fd-glibc-datagram-socket.c | 52 + .../analyzer/fd-glibc-make_named_socket.h | 47 + gcc/testsuite/gcc.dg/analyzer/fd-listen.c | 63 + .../analyzer/fd-manpage-getaddrinfo-client.c | 122 ++ .../analyzer/fd-mappage-getaddrinfo-server.c | 119 ++ .../gcc.dg/analyzer/fd-socket-meaning.c | 21 + .../gcc.dg/analyzer/fd-socket-misuse.c | 98 ++ .../analyzer/fd-stream-socket-active-open.c | 74 ++ .../analyzer/fd-stream-socket-passive-open.c | 197 +++ .../gcc.dg/analyzer/fd-stream-socket.c | 98 ++ .../gcc.dg/analyzer/fd-symbolic-socket.c | 98 ++ gcc/testsuite/gcc.dg/analyzer/pr104369-1.c | 4 +- gcc/testsuite/gcc.dg/analyzer/pr104369-2.c | 3 + 30 files changed, 3007 insertions(+), 60 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-accept.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-bind.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-connect.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-listen.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c