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 <dmalc...@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 diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc index ba4352b729a..0629b681e7c 100644 --- a/gcc/analyzer/analyzer-language.cc +++ b/gcc/analyzer/analyzer-language.cc @@ -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. diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index dbab3b82deb..bdf3ea65d00 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -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. diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 18df44017aa..290ee73c8e3 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -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. */ diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 8e3c160189c..c9baad8e036 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -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; } diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index e8ca71b66ed..17aaaec7210 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -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 diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index aa8085b72b8..8951e88ed05 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -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))) { diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 2a0e16873b3..ae4dd6eb4fb 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -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 diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc index 370115d56bf..d0b587143d0 100644 --- a/gcc/analyzer/sm-fd.cc +++ b/gcc/analyzer/sm-fd.cc @@ -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 diff --git a/gcc/analyzer/sm-fd.dot b/gcc/analyzer/sm-fd.dot index 175daae44ae..0b2b31996ec 100644 --- a/gcc/analyzer/sm-fd.dot +++ b/gcc/analyzer/sm-fd.dot @@ -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')"]; } diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 94a2e20cfc1..cf82f6d338b 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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: diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-accept.c b/gcc/testsuite/gcc.dg/analyzer/fd-accept.c new file mode 100644 index 00000000000..36cc7af7184 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-accept.c @@ -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; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-bind.c b/gcc/testsuite/gcc.dg/analyzer/fd-bind.c new file mode 100644 index 00000000000..6f91bc4b794 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-bind.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-connect.c b/gcc/testsuite/gcc.dg/analyzer/fd-connect.c new file mode 100644 index 00000000000..1ab54d01f36 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-connect.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c new file mode 100644 index 00000000000..045bdfa32d3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c new file mode 100644 index 00000000000..1ff902894af --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c @@ -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); + } + } + } + } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c new file mode 100644 index 00000000000..f96da8101cc --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c new file mode 100644 index 00000000000..888c751e88d --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c new file mode 100644 index 00000000000..b8b68768c34 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c @@ -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); + } + } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h new file mode 100644 index 00000000000..bdb6de0ae15 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h @@ -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; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-listen.c b/gcc/testsuite/gcc.dg/analyzer/fd-listen.c new file mode 100644 index 00000000000..1f54a8f2953 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-listen.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c b/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c new file mode 100644 index 00000000000..d9c3ff05de8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c @@ -0,0 +1,122 @@ +/* Example from getaddrinfo.3 manpage, which has this license: + +Copyright (c) 2007, 2008 Michael Kerrisk <mtk.manpa...@gmail.com> +and Copyright (c) 2006 Ulrich Drepper <drep...@redhat.com> +A few pieces of an earlier version remain: +Copyright 2000, Sam Varshavchik <mr...@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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c b/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c new file mode 100644 index 00000000000..66398e834cc --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c @@ -0,0 +1,119 @@ +/* Example from getaddrinfo.3 manpage, which has this license: + +Copyright (c) 2007, 2008 Michael Kerrisk <mtk.manpa...@gmail.com> +and Copyright (c) 2006 Ulrich Drepper <drep...@redhat.com> +A few pieces of an earlier version remain: +Copyright 2000, Sam Varshavchik <mr...@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"); + } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c b/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c new file mode 100644 index 00000000000..5bfb57f68fb --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c @@ -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'" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c b/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c new file mode 100644 index 00000000000..4ff08d5ec19 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c @@ -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 } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c new file mode 100644 index 00000000000..7fde0ef6285 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c @@ -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'" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c new file mode 100644 index 00000000000..c31e5b5eefb --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c @@ -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'" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c new file mode 100644 index 00000000000..3a292d0e2d2 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c new file mode 100644 index 00000000000..83400c18f50 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c b/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c index d3b32418a9e..c05137bb219 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c @@ -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) diff --git a/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c b/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c index 57dc9caf3e9..93d9987d0ba 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c @@ -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 -- 2.26.3