public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r13-1404] analyzer: implement five new warnings for misuse of POSIX file descriptor APIs [PR106003].
@ 2022-07-02 16:45 Immad Mir
0 siblings, 0 replies; only message in thread
From: Immad Mir @ 2022-07-02 16:45 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:97baacba963c06e3d0e33cde04e7e687671e60e7
commit r13-1404-g97baacba963c06e3d0e33cde04e7e687671e60e7
Author: Immad Mir <mirimmad17@gmail.com>
Date: Sat Jul 2 22:09:37 2022 +0530
analyzer: implement five new warnings for misuse of POSIX file descriptor APIs [PR106003].
This patch adds a new state machine to the analyzer for checking usage of POSIX file descriptor
APIs with five new warnings.
It adds:
- check for FD leaks (CWE 775).
- check for double "close" of a FD (CWE-1341).
- check for read/write of a closed file descriptor.
- check whether a file descriptor was used without being checked for validity.
- check for read/write of a descriptor opened for just writing/reading.
gcc/ChangeLog:
PR analyzer/106003
* Makefile.in (ANALYZER_OBJS): Add sm-fd.o.
* doc/invoke.texi: Add -Wanalyzer-fd-double-close, -Wanalyzer-fd-leak,
-Wanalyzer-fd-access-mode-mismatch, -Wanalyzer-fd-use-without-check,
-Wanalyzer-fd-use-after-close.
gcc/analyzer/ChangeLog:
PR analyzer/106003
* analyzer.opt (Wanalyzer-fd-leak): New option.
(Wanalyzer-fd-access-mode-mismatch): New option.
(Wanalyzer-fd-use-without-check): New option.
(Wanalyzer-fd-double-close): New option.
(Wanalyzer-fd-use-after-close): New option.
* sm.h (make_fd_state_machine): New decl.
* sm.cc (make_checkers): Call make_fd_state_machine.
* sm-fd.cc: New file.
gcc/testsuite/ChangeLog:
PR analyzer/106003
* gcc.dg/analyzer/fd-1.c: New test.
* gcc.dg/analyzer/fd-2.c: New test.
* gcc.dg/analyzer/fd-3.c: New test.
* gcc.dg/analyzer/fd-4.c: New test.
Diff:
---
gcc/Makefile.in | 1 +
gcc/analyzer/analyzer.opt | 20 +
gcc/analyzer/sm-fd.cc | 847 +++++++++++++++++++++++++++++++++++
gcc/analyzer/sm.cc | 1 +
gcc/analyzer/sm.h | 1 +
gcc/doc/invoke.texi | 55 +++
gcc/testsuite/gcc.dg/analyzer/fd-1.c | 39 ++
gcc/testsuite/gcc.dg/analyzer/fd-2.c | 49 ++
gcc/testsuite/gcc.dg/analyzer/fd-3.c | 85 ++++
gcc/testsuite/gcc.dg/analyzer/fd-4.c | 62 +++
10 files changed, 1160 insertions(+)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index a82909dafe5..69ac81a1e45 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1273,6 +1273,7 @@ ANALYZER_OBJS = \
analyzer/region-model-reachability.o \
analyzer/sm.o \
analyzer/sm-file.o \
+ analyzer/sm-fd.o \
analyzer/sm-malloc.o \
analyzer/sm-pattern-test.o \
analyzer/sm-sensitive.o \
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 4aea52d3a87..8ef6a6fa13c 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -66,6 +66,26 @@ Wanalyzer-exposure-through-output-file
Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning
Warn about code paths in which sensitive data is written to a file.
+Wanalyzer-fd-access-mode-mismatch
+Common Var(warn_analyzer_fd_mode_mismatch) Init(1) Warning
+Warn about code paths in which read on a write-only file descriptor is attempted, or vice versa.
+
+Wanalyzer-fd-double-close
+Common Var(warn_analyzer_fd_double_close) Init(1) Warning
+Warn about code paths in which a file descriptor can be closed more than once.
+
+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-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.
+
+Wanalyzer-fd-use-without-check
+Common Var(warn_analyzer_fd_use_without_check) Init(1) Warning
+Warn about code paths in which a file descriptor is used without being checked for validity.
+
Wanalyzer-file-leak
Common Var(warn_analyzer_file_leak) Init(1) Warning
Warn about code paths in which a stdio FILE is not closed.
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
new file mode 100644
index 00000000000..4058ac53308
--- /dev/null
+++ b/gcc/analyzer/sm-fd.cc
@@ -0,0 +1,847 @@
+/* A state machine for detecting misuses of POSIX file descriptor APIs.
+ Copyright (C) 2019-2022 Free Software Foundation, Inc.
+ Contributed by Immad Mir <mir@sourceware.org>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "options.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "function.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/function-set.h"
+#include "analyzer/analyzer-selftests.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+namespace {
+
+/* An enum for distinguishing between three different access modes. */
+
+enum access_mode
+{
+ READ_WRITE,
+ READ_ONLY,
+ WRITE_ONLY
+};
+
+class fd_state_machine : public state_machine
+{
+public:
+ fd_state_machine (logger *logger);
+
+ bool
+ inherited_state_p () const final override
+ {
+ return false;
+ }
+
+ state_machine::state_t
+ get_default_state (const svalue *sval) const final override
+ {
+ if (tree cst = sval->maybe_get_constant ())
+ {
+ if (TREE_CODE (cst) == INTEGER_CST)
+ {
+ int val = TREE_INT_CST_LOW (cst);
+ if (val >= 0)
+ return m_constant_fd;
+ else
+ return m_invalid;
+ }
+ }
+ return m_start;
+ }
+
+ bool on_stmt (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt) const final override;
+
+ void on_condition (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const svalue *lhs, const tree_code op,
+ const svalue *rhs) const final override;
+
+ bool can_purge_p (state_t s) const final override;
+ pending_diagnostic *on_leak (tree var) const final override;
+
+ bool is_unchecked_fd_p (state_t s) const;
+ bool is_valid_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;
+ bool is_writeonly_fd_p (state_t s) const;
+ enum access_mode get_access_mode_from_flag (int flag) const;
+
+ /* State for a constant file descriptor (>= 0) */
+ state_t m_constant_fd;
+
+ /* States representing a file descriptor that hasn't yet been
+ checked for validity after opening, for three different
+ access modes. */
+ state_t m_unchecked_read_write;
+
+ state_t m_unchecked_read_only;
+
+ state_t m_unchecked_write_only;
+
+ /* States for representing a file descriptor that is known to be valid (>=
+ 0), for three different access modes.*/
+ state_t m_valid_read_write;
+
+ state_t m_valid_read_only;
+
+ state_t m_valid_write_only;
+
+ /* State for a file descriptor that is known to be invalid (< 0). */
+ state_t m_invalid;
+
+ /* State for a file descriptor that has been closed.*/
+ state_t m_closed;
+
+ /* State for a file descriptor that we do not want to track anymore . */
+ state_t m_stop;
+
+private:
+ void on_open (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
+ const gcall *call) const;
+ void on_close (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
+ const gcall *call) const;
+ void on_read (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
+ const gcall *call, const tree callee_fndecl) const;
+ void on_write (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
+ const gcall *call, const tree callee_fndecl) const;
+ void check_for_open_fd (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const gcall *call,
+ const tree callee_fndecl,
+ enum access_direction access_fn) const;
+
+ void make_valid_transitions_on_condition (sm_context *sm_ctxt,
+ const supernode *node,
+ const gimple *stmt,
+ const svalue *lhs) const;
+ void make_invalid_transitions_on_condition (sm_context *sm_ctxt,
+ const supernode *node,
+ const gimple *stmt,
+ const svalue *lhs) const;
+};
+
+/* Base diagnostic class relative to fd_state_machine. */
+class fd_diagnostic : public pending_diagnostic
+{
+public:
+ fd_diagnostic (const fd_state_machine &sm, tree arg) : m_sm (sm), m_arg (arg)
+ {
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const override
+ {
+ return same_tree_p (m_arg, ((const fd_diagnostic &)base_other).m_arg);
+ }
+
+ 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))
+ {
+ if (change.m_new_state == m_sm.m_unchecked_read_write)
+ return change.formatted_print ("opened here as read-write");
+
+ if (change.m_new_state == m_sm.m_unchecked_read_only)
+ return change.formatted_print ("opened here as read-only");
+
+ if (change.m_new_state == m_sm.m_unchecked_write_only)
+ return change.formatted_print ("opened here as write-only");
+ }
+
+ if (change.m_new_state == m_sm.m_closed)
+ return change.formatted_print ("closed here");
+
+ if (m_sm.is_unchecked_fd_p (change.m_old_state)
+ && m_sm.is_valid_fd_p (change.m_new_state))
+ {
+ if (change.m_expr)
+ return change.formatted_print (
+ "assuming %qE is a valid file descriptor (>= 0)", change.m_expr);
+ else
+ return change.formatted_print ("assuming a valid file descriptor");
+ }
+
+ if (m_sm.is_unchecked_fd_p (change.m_old_state)
+ && change.m_new_state == m_sm.m_invalid)
+ {
+ if (change.m_expr)
+ return change.formatted_print (
+ "assuming %qE is an invalid file descriptor (< 0)",
+ change.m_expr);
+ else
+ return change.formatted_print ("assuming an invalid file descriptor");
+ }
+
+ return label_text ();
+ }
+
+protected:
+ const fd_state_machine &m_sm;
+ tree m_arg;
+};
+
+class fd_leak : public fd_diagnostic
+{
+public:
+ fd_leak (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg) {}
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_leak";
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_leak;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ /*CWE-775: Missing Release of File Descriptor or Handle after Effective
+ Lifetime
+ */
+ diagnostic_metadata m;
+ m.add_cwe (775);
+ if (m_arg)
+ return warning_meta (rich_loc, m, get_controlling_option (),
+ "leak of file descriptor %qE", m_arg);
+ else
+ return warning_meta (rich_loc, m, get_controlling_option (),
+ "leak of file descriptor");
+ }
+
+ label_text
+ describe_state_change (const evdesc::state_change &change) final override
+ {
+ if (m_sm.is_unchecked_fd_p (change.m_new_state))
+ {
+ m_open_event = change.m_event_id;
+ return label_text::borrow ("opened here");
+ }
+
+ return fd_diagnostic::describe_state_change (change);
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ if (m_open_event.known_p ())
+ {
+ if (ev.m_expr)
+ return ev.formatted_print ("%qE leaks here; was opened at %@",
+ ev.m_expr, &m_open_event);
+ else
+ return ev.formatted_print ("leaks here; was opened at %@",
+ &m_open_event);
+ }
+ else
+ {
+ if (ev.m_expr)
+ return ev.formatted_print ("%qE leaks here", ev.m_expr);
+ else
+ return ev.formatted_print ("leaks here");
+ }
+ }
+
+private:
+ diagnostic_event_id_t m_open_event;
+};
+
+class fd_access_mode_mismatch : public fd_diagnostic
+{
+public:
+ fd_access_mode_mismatch (const fd_state_machine &sm, tree arg,
+ enum access_direction fd_dir,
+ const tree callee_fndecl)
+ : fd_diagnostic (sm, arg), m_fd_dir (fd_dir),
+ m_callee_fndecl (callee_fndecl)
+
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_access_mode_mismatch";
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_access_mode_mismatch;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ switch (m_fd_dir)
+ {
+ case DIR_READ:
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on %<read-only%> file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ case DIR_WRITE:
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on %<write-only%> file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ default:
+ gcc_unreachable ();
+ }
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const override
+ {
+ const fd_access_mode_mismatch &sub_other
+ = (const fd_access_mode_mismatch &)base_other;
+ return (same_tree_p (m_arg, sub_other.m_arg)
+ && m_callee_fndecl == sub_other.m_callee_fndecl
+ && m_fd_dir == sub_other.m_fd_dir);
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ switch (m_fd_dir)
+ {
+ case DIR_READ:
+ return ev.formatted_print ("%qE on %<read-only%> file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ case DIR_WRITE:
+ return ev.formatted_print ("%qE on %<write-only%> file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ default:
+ gcc_unreachable ();
+ }
+ }
+
+private:
+ enum access_direction m_fd_dir;
+ const tree m_callee_fndecl;
+};
+
+class double_close : public fd_diagnostic
+{
+public:
+ double_close (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "double_close";
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_double_close;
+ }
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ diagnostic_metadata m;
+ // CWE-1341: Multiple Releases of Same Resource or Handle
+ m.add_cwe (1341);
+ return warning_meta (rich_loc, m, get_controlling_option (),
+ "double %<close%> of file descriptor %qE", m_arg);
+ }
+
+ label_text
+ describe_state_change (const evdesc::state_change &change) override
+ {
+ if (m_sm.is_unchecked_fd_p (change.m_new_state))
+ return label_text::borrow ("opened here");
+
+ if (change.m_new_state == m_sm.m_closed)
+ {
+ m_first_close_event = change.m_event_id;
+ return change.formatted_print ("first %qs here", "close");
+ }
+ return fd_diagnostic::describe_state_change (change);
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ if (m_first_close_event.known_p ())
+ return ev.formatted_print ("second %qs here; first %qs was at %@",
+ "close", "close", &m_first_close_event);
+ return ev.formatted_print ("second %qs here", "close");
+ }
+
+private:
+ diagnostic_event_id_t m_first_close_event;
+};
+
+class fd_use_after_close : public fd_diagnostic
+{
+public:
+ fd_use_after_close (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl)
+ : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "fd_use_after_close";
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_use_after_close;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on closed file descriptor %qE", m_callee_fndecl,
+ m_arg);
+ }
+
+ label_text
+ describe_state_change (const evdesc::state_change &change) override
+ {
+ if (m_sm.is_unchecked_fd_p (change.m_new_state))
+ return label_text::borrow ("opened here");
+
+ if (change.m_new_state == m_sm.m_closed)
+ return change.formatted_print ("closed here");
+
+ return fd_diagnostic::describe_state_change (change);
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ return ev.formatted_print ("%qE on closed file descriptor %qE here",
+ m_callee_fndecl, m_arg);
+ }
+
+private:
+ const tree m_callee_fndecl;
+};
+
+class unchecked_use_of_fd : public fd_diagnostic
+{
+public:
+ unchecked_use_of_fd (const fd_state_machine &sm, tree arg,
+ const tree callee_fndecl)
+ : fd_diagnostic (sm, arg), m_callee_fndecl (callee_fndecl)
+ {
+ }
+
+ const char *
+ get_kind () const final override
+ {
+ return "unchecked_use_of_fd";
+ }
+
+ int
+ get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_fd_use_without_check;
+ }
+
+ bool
+ emit (rich_location *rich_loc) final override
+ {
+ return warning_at (rich_loc, get_controlling_option (),
+ "%qE on possibly invalid file descriptor %qE",
+ m_callee_fndecl, m_arg);
+ }
+
+ bool
+ subclass_equal_p (const pending_diagnostic &base_other) const override
+ {
+ const unchecked_use_of_fd &sub_other
+ = (const unchecked_use_of_fd &)base_other;
+ return (same_tree_p (m_arg, sub_other.m_arg)
+ && m_callee_fndecl == sub_other.m_callee_fndecl);
+ }
+
+ label_text
+ describe_state_change (const evdesc::state_change &change) override
+ {
+ if (m_sm.is_unchecked_fd_p (change.m_new_state))
+ {
+ m_first_open_event = change.m_event_id;
+ return label_text::borrow ("opened here");
+ }
+
+ return fd_diagnostic::describe_state_change (change);
+ }
+
+ label_text
+ describe_final_event (const evdesc::final_event &ev) final override
+ {
+ if (m_first_open_event.known_p ())
+ return ev.formatted_print (
+ "%qE could be invalid: unchecked value from %@", m_arg,
+ &m_first_open_event);
+ else
+ return ev.formatted_print ("%qE could be invalid", m_arg);
+ }
+
+private:
+ diagnostic_event_id_t m_first_open_event;
+ const tree m_callee_fndecl;
+};
+
+fd_state_machine::fd_state_machine (logger *logger)
+ : state_machine ("file-descriptor", logger),
+ m_constant_fd (add_state ("fd-constant")),
+ m_unchecked_read_write (add_state ("fd-unchecked-read-write")),
+ m_unchecked_read_only (add_state ("fd-unchecked-read-only")),
+ m_unchecked_write_only (add_state ("fd-unchecked-write-only")),
+ m_invalid (add_state ("fd-invalid")),
+ m_valid_read_write (add_state ("fd-valid-read-write")),
+ m_valid_read_only (add_state ("fd-valid-read-only")),
+ m_valid_write_only (add_state ("fd-valid-write-only")),
+ m_closed (add_state ("fd-closed")), m_stop (add_state ("fd-stop"))
+{
+}
+
+bool
+fd_state_machine::is_unchecked_fd_p (state_t s) const
+{
+ return (s == m_unchecked_read_write
+ || s == m_unchecked_read_only
+ || s == m_unchecked_write_only);
+}
+
+bool
+fd_state_machine::is_valid_fd_p (state_t s) const
+{
+ return (s == m_valid_read_write
+ || s == m_valid_read_only
+ || s == m_valid_write_only);
+}
+
+enum access_mode
+fd_state_machine::get_access_mode_from_flag (int flag) const
+{
+ /* FIXME: this code assumes the access modes on the host and
+ target are the same, which in practice might not be the case. */
+
+ if ((flag & O_ACCMODE) == O_RDONLY)
+ {
+ return READ_ONLY;
+ }
+ else if ((flag & O_ACCMODE) == O_WRONLY)
+ {
+ return WRITE_ONLY;
+ }
+ return READ_WRITE;
+}
+
+bool
+fd_state_machine::is_readonly_fd_p (state_t state) const
+{
+ return (state == m_unchecked_read_only || state == m_valid_read_only);
+}
+
+bool
+fd_state_machine::is_writeonly_fd_p (state_t state) const
+{
+ return (state == m_unchecked_write_only || state == m_valid_write_only);
+}
+
+bool
+fd_state_machine::is_closed_fd_p (state_t state) const
+{
+ return (state == m_closed);
+}
+
+bool
+fd_state_machine::is_constant_fd_p (state_t state) const
+{
+ return (state == m_constant_fd);
+}
+
+bool
+fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt) const
+{
+ if (const gcall *call = dyn_cast<const gcall *> (stmt))
+ if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
+ {
+ if (is_named_call_p (callee_fndecl, "open", call, 2))
+ {
+ on_open (sm_ctxt, node, stmt, call);
+ return true;
+ } // "open"
+
+ if (is_named_call_p (callee_fndecl, "close", call, 1))
+ {
+ on_close (sm_ctxt, node, stmt, call);
+ return true;
+ } // "close"
+
+ if (is_named_call_p (callee_fndecl, "write", call, 3))
+ {
+ on_write (sm_ctxt, node, stmt, call, callee_fndecl);
+ return true;
+ } // "write"
+
+ if (is_named_call_p (callee_fndecl, "read", call, 3))
+ {
+ on_read (sm_ctxt, node, stmt, call, callee_fndecl);
+ return true;
+ } // "read"
+ }
+
+ return false;
+}
+
+void
+fd_state_machine::on_open (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const gcall *call) const
+{
+ tree lhs = gimple_call_lhs (call);
+ if (lhs)
+ {
+ tree arg = gimple_call_arg (call, 1);
+ if (TREE_CODE (arg) == INTEGER_CST)
+ {
+ int flag = TREE_INT_CST_LOW (arg);
+ enum access_mode mode = get_access_mode_from_flag (flag);
+
+ switch (mode)
+ {
+ case READ_ONLY:
+ sm_ctxt->on_transition (node, stmt, lhs, m_start,
+ m_unchecked_read_only);
+ break;
+ case WRITE_ONLY:
+ sm_ctxt->on_transition (node, stmt, lhs, m_start,
+ m_unchecked_write_only);
+ break;
+ default:
+ sm_ctxt->on_transition (node, stmt, lhs, m_start,
+ m_unchecked_read_write);
+ }
+ }
+ }
+ else
+ {
+ sm_ctxt->warn (node, stmt, NULL_TREE, new fd_leak (*this, NULL_TREE));
+ }
+}
+
+void
+fd_state_machine::on_close (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const gcall *call) const
+{
+ tree arg = gimple_call_arg (call, 0);
+ state_t state = sm_ctxt->get_state (stmt, arg);
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+
+ sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_unchecked_read_write, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_unchecked_read_only, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_unchecked_write_only, m_closed);
+ sm_ctxt->on_transition (node, stmt, arg, m_valid_read_write, m_closed);
+ 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);
+
+ if (is_closed_fd_p (state))
+ {
+ sm_ctxt->warn (node, stmt, arg, new double_close (*this, diag_arg));
+ sm_ctxt->set_next_state (stmt, arg, m_stop);
+ }
+}
+void
+fd_state_machine::on_read (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const gcall *call,
+ const tree callee_fndecl) const
+{
+ check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIR_READ);
+}
+void
+fd_state_machine::on_write (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const gcall *call,
+ const tree callee_fndecl) const
+{
+ check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl, DIR_WRITE);
+}
+
+void
+fd_state_machine::check_for_open_fd (
+ sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
+ const gcall *call, const tree callee_fndecl,
+ enum access_direction callee_fndecl_dir) const
+{
+ tree arg = gimple_call_arg (call, 0);
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+ state_t state = sm_ctxt->get_state (stmt, arg);
+
+ if (is_closed_fd_p (state))
+ {
+ sm_ctxt->warn (node, stmt, arg,
+ new fd_use_after_close (*this, diag_arg, callee_fndecl));
+ }
+
+ else
+ {
+ if (!(is_valid_fd_p (state) || (state == m_stop)))
+ {
+ if (!is_constant_fd_p (state))
+ sm_ctxt->warn (
+ node, stmt, arg,
+ new unchecked_use_of_fd (*this, diag_arg, callee_fndecl));
+ }
+ switch (callee_fndecl_dir)
+ {
+ case DIR_READ:
+ if (is_writeonly_fd_p (state))
+ {
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+ sm_ctxt->warn (node, stmt, arg,
+ new fd_access_mode_mismatch (
+ *this, diag_arg, DIR_WRITE, callee_fndecl));
+ }
+
+ break;
+ case DIR_WRITE:
+
+ if (is_readonly_fd_p (state))
+ {
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+ sm_ctxt->warn (node, stmt, arg,
+ new fd_access_mode_mismatch (
+ *this, diag_arg, DIR_READ, callee_fndecl));
+ }
+ break;
+ }
+ }
+}
+
+void
+fd_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node,
+ const gimple *stmt, const svalue *lhs,
+ enum tree_code op, const svalue *rhs) const
+{
+ if (tree cst = rhs->maybe_get_constant ())
+ {
+ if (TREE_CODE (cst) == INTEGER_CST)
+ {
+ int val = TREE_INT_CST_LOW (cst);
+ if (val == -1)
+ {
+ if (op == NE_EXPR)
+ make_valid_transitions_on_condition (sm_ctxt, node, stmt, lhs);
+
+ else if (op == EQ_EXPR)
+ make_invalid_transitions_on_condition (sm_ctxt, node, stmt,
+ lhs);
+ }
+ }
+ }
+
+ if (rhs->all_zeroes_p ())
+ {
+ if (op == GE_EXPR)
+ make_valid_transitions_on_condition (sm_ctxt, node, stmt, lhs);
+ else if (op == LT_EXPR)
+ make_invalid_transitions_on_condition (sm_ctxt, node, stmt, lhs);
+ }
+}
+
+void
+fd_state_machine::make_valid_transitions_on_condition (sm_context *sm_ctxt,
+ const supernode *node,
+ const gimple *stmt,
+ const svalue *lhs) const
+{
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write,
+ m_valid_read_write);
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only,
+ m_valid_read_only);
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only,
+ m_valid_write_only);
+}
+
+void
+fd_state_machine::make_invalid_transitions_on_condition (
+ sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
+ const svalue *lhs) const
+{
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write, m_invalid);
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only, m_invalid);
+ sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only, m_invalid);
+}
+
+bool
+fd_state_machine::can_purge_p (state_t s) const
+{
+ if (is_unchecked_fd_p (s) || is_valid_fd_p (s))
+ return false;
+ else
+ return true;
+}
+
+pending_diagnostic *
+fd_state_machine::on_leak (tree var) const
+{
+ return new fd_leak (*this, var);
+}
+} // namespace
+
+state_machine *
+make_fd_state_machine (logger *logger)
+{
+ return new fd_state_machine (logger);
+}
+} // namespace ana
+
+#endif // ENABLE_ANALYZER
\ No newline at end of file
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
index 622cb0b7ab3..24c20b894cd 100644
--- a/gcc/analyzer/sm.cc
+++ b/gcc/analyzer/sm.cc
@@ -167,6 +167,7 @@ make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
{
out.safe_push (make_malloc_state_machine (logger));
out.safe_push (make_fileptr_state_machine (logger));
+ out.safe_push (make_fd_state_machine (logger));
/* The "taint" checker must be explicitly enabled (as it currently
leads to state explosions that stop the other checkers working). */
if (flag_analyzer_checker)
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index 4cc54531c56..e80ef1fac37 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -301,6 +301,7 @@ extern state_machine *make_sensitive_state_machine (logger *logger);
extern state_machine *make_signal_state_machine (logger *logger);
extern state_machine *make_pattern_test_state_machine (logger *logger);
extern state_machine *make_va_list_state_machine (logger *logger);
+extern state_machine *make_fd_state_machine (logger *logger);
} // namespace ana
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 757775ea576..d86e45ac982 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -9742,6 +9742,11 @@ Enabling this option effectively enables the following warnings:
-Wanalyzer-double-fclose @gol
-Wanalyzer-double-free @gol
-Wanalyzer-exposure-through-output-file @gol
+-Wanalyzer-fd-access-mode-mismatch @gol
+-Wanalyzer-fd-double-close @gol
+-Wanalyzer-fd-leak @gol
+-Wanalyzer-fd-use-after-close @gol
+-Wanalyzer-fd-use-without-check @gol
-Wanalyzer-file-leak @gol
-Wanalyzer-free-of-non-heap @gol
-Wanalyzer-malloc-leak @gol
@@ -9816,6 +9821,56 @@ This diagnostic warns for paths through the code in which a
security-sensitive value is written to an output file
(such as writing a password to a log file).
+@item -Wno-analyzer-fd-access-mode-mismatch
+@opindex Wanalyzer-fd-access-mode-mismatch
+@opindex Wno-analyzer-fd-access-mode-mismatch
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-access-mode-mismatch}
+to disable it.
+
+This diagnostic warns for paths through code in which a
+@code{read} on a write-only file descriptor is attempted, or vice versa
+
+@item -Wno-analyzer-fd-double-close
+@opindex Wanalyzer-fd-double-close
+@opindex Wno-analyzer-fd-double-close
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-double-close}
+to disable it.
+
+This diagnostic warns for paths through code in which a
+file descriptor can be closed more than once.
+
+@item -Wno-analyzer-fd-leak
+@opindex Wanalyzer-fd-leak
+@opindex Wno-analyzer-fd-leak
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-leak}
+to disable it.
+
+This diagnostic warns for paths through code in which an
+open file descriptor is leaked.
+
+@item -Wno-analyzer-fd-use-after-close
+@opindex Wanalyzer-fd-use-after-close
+@opindex Wno-analyzer-fd-use-after-close
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-use-after-close}
+to disable it.
+
+This diagnostic warns for paths through code in which a
+read or write is called on a closed file descriptor.
+
+@item -Wno-analyzer-fd-use-without-check
+@opindex Wanalyzer-fd-use-without-check
+@opindex Wno-analyzer-fd-use-without-check
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-fd-use-without-check}
+to disable it.
+
+This diagnostic warns for paths through code in which a
+file descriptor is used without being checked for validity.
+
@item -Wno-analyzer-file-leak
@opindex Wanalyzer-file-leak
@opindex Wno-analyzer-file-leak
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-1.c b/gcc/testsuite/gcc.dg/analyzer/fd-1.c
new file mode 100644
index 00000000000..8a72e63833c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-1.c
@@ -0,0 +1,39 @@
+int open(const char *, int mode);
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+
+void
+test_1 (const char *path)
+{
+ int fd = open (path, O_RDONLY); /* { dg-message "\\(1\\) opened here" } */
+ return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]" "warning" } */
+ /* { dg-message "\\(2\\) 'fd' leaks here; was opened at \\(1\\)" "event" { target *-*-* } .-1 } */
+}
+
+void
+test_2 (const char *path)
+{
+ int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+ if (fd >= 0) /* { dg-message "\\(2\\) assuming 'fd' is a valid file descriptor" "event1" } */
+ /* { dg-message "\\(3\\) following 'true' branch \\(when 'fd >= 0'\\)..." "event2" { target *-*-* } .-1 } */
+ {
+ return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]" "warning" } */
+ /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1 } */
+ /* { dg-message "\\(5\\) 'fd' leaks here; was opened at \\(1\\)" "event2" { target *-*-* } .-2 } */
+ }
+}
+
+void
+test_3 (const char *path)
+{
+ int fd = open (path, O_WRONLY); /* { dg-message "\\(1\\) opened here" } */
+ return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]" "warning" } */
+}
+
+void test_4 (const char *path)
+{
+ open(path, O_RDONLY); /* { dg-warning "leak of file descriptor \\\[CWE-775\\\]" } */
+ /* { dg-message "\\(1\\) leaks here" "" { target *-*-* } .-1 } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-2.c b/gcc/testsuite/gcc.dg/analyzer/fd-2.c
new file mode 100644
index 00000000000..96ccf2f7ba8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-2.c
@@ -0,0 +1,49 @@
+int open(const char *, int mode);
+void close(int fd);
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+#define STDIN 0
+
+void
+test_1 (const char *path)
+{
+ int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+ close (fd); /* { dg-message "\\(2\\) first 'close' here" "event1" } */
+ close (fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" "warning" } */
+ /* { dg-message "\\(3\\) second 'close' here; first 'close' was at \\(2\\)" "event2" { target *-*-* } .-1 } */
+}
+
+void
+test_2 (const char *path)
+{
+ int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+ if (fd < 0) /* { dg-message "\\(2\\) assuming 'fd' is a valid file descriptor \\(>= 0\\)" "event1" } */
+ /* { dg-message "\\(3\\) following 'false' branch \\(when 'fd >= 0'\\)..." "event2" { target *-*-* } .-1 } */
+ return;
+ close (fd); /* { dg-message "\\(4\\) ...to here" "event1" } */
+ /* { dg-message "\\(5\\) first 'close' here" "event2" { target *-*-* } .-1 } */
+ close (fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" "warning" } */
+ /* {dg-message "\\(6\\) second 'close' here; first was at \\(5\\)" "" { target *-*-* } .-1 } */
+}
+
+void
+test_3 ()
+{
+ /* FD 0 is stdin at the entry to "main" and thus read-only, but we have no
+ guarantees here that it hasn't been closed and then reopened for
+ writing, so we can't issue a warning */
+
+ int fd = STDIN;
+ close(fd); /* { dg-message "\\(1\\) first 'close' here" } */
+ close(fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" "warning" } */
+ /* { dg-message "\\(2\\) second 'close' here; first 'close' was at \\(1\\)" "event2" { target *-*-* } .-1 } */
+}
+
+void
+test_4 ()
+{
+ int fd = -1;
+ close(fd);
+ close(fd);
+}
\ No newline at end of file
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-3.c b/gcc/testsuite/gcc.dg/analyzer/fd-3.c
new file mode 100644
index 00000000000..40fc8af27b5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-3.c
@@ -0,0 +1,85 @@
+int open(const char *, int mode);
+void close(int fd);
+int write (int fd, void *buf, int nbytes);
+int read (int fd, void *buf, int nbytes);
+int some_condition();
+
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+#define STDIN 0
+#define O_NOATIME 262144
+
+void
+test_1 (const char *path, void *buf)
+{
+ int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+ write (fd, buf, 1); /* { dg-message "\\(2\\) 'fd' could be invalid: unchecked value from \\(1\\)" } */
+ /* { dg-warning "'write' on possibly invalid file descriptor 'fd'" "warning" { target *-*-* } .-1 } */
+ close(fd);
+}
+
+void
+test_2 (const char *path, void *buf)
+{
+ int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+ read (fd, buf, 1); /* { dg-message "\\(2\\) 'fd' could be invalid: unchecked value from \\(1\\)" } */
+ /* { dg-warning "'read' on possibly invalid file descriptor 'fd'" "warning" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+void
+test_3 (void *buf)
+{
+ int fd = -1;
+ read (fd, buf, 1); /* { dg-warning "'read' on possibly invalid file descriptor 'fd'" } */
+ /* { dg-message "\\(1\\) 'fd' could be invalid" "" { target *-*-* } .-1 } */
+}
+
+void
+test_4 (void *buf)
+{
+ int fd = STDIN;
+ read (fd, buf, 1);
+ close(fd);
+}
+
+void
+test_5 (char *path, void *buf)
+{
+ int flags = O_RDONLY;
+ if (some_condition())
+ flags |= O_NOATIME;
+ int fd = open (path, flags);
+ read (fd, buf, 1); /* { dg-warning "'read' on possibly invalid file descriptor 'fd'" } */
+ /* { dg-message "\\(1\\) 'fd' could be invalid" "" { target *-*-* } .-1 } */
+ close (fd);
+}
+
+
+void
+test_6 (char *path, void *buf)
+{
+ int fd = open (path, O_RDONLY);
+ if (fd != -1)
+ {
+ read (fd, buf, 1);
+ }
+ close (fd);
+}
+
+
+void
+test_7 (char *path, void *buf)
+{
+ int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+ if (fd != -1) /* { dg-message "\\(2\\) assuming 'fd' is an invalid file descriptor \\(< 0\\)" } */
+ {
+ read (fd, buf, 1);
+ } else
+ {
+ write (fd, buf, 1); /* { dg-warning "'write' on possibly invalid file descriptor 'fd'" } */
+
+ }
+ close(fd);
+}
\ No newline at end of file
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-4.c b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
new file mode 100644
index 00000000000..a973704f403
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-4.c
@@ -0,0 +1,62 @@
+int open(const char *, int mode);
+void close(int fd);
+int write (int fd, void *buf, int nbytes);
+int read (int fd, void *buf, int nbytes);
+
+#define O_RDONLY 0
+#define O_WRONLY 1
+#define O_RDWR 2
+
+
+void
+test_1 (const char *path, void *buf)
+{
+ int fd = open (path, O_RDONLY); /* { dg-message "opened here as read-only" } */
+ if (fd >= 0) /* { dg-message "assuming 'fd' is a valid file descriptor \\(>= 0\\)" "event1" } */
+ /* { dg-message "following 'true' branch \\(when 'fd >= 0'\\)..." "event2" { target *-*-* } .-1 } */
+ {
+ write (fd, buf, 1); /* { dg-warning "'write' on 'read-only' file descriptor 'fd'" "warning" } */
+ /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1 } */
+ /* { dg-message "\\(5\\) 'write' on 'read-only' file descriptor 'fd'" "event2" { target *-*-* } .-2 } */
+ close (fd);
+ }
+}
+
+void
+test_2 (const char *path, void *buf)
+{
+ int fd = open (path, O_WRONLY); /* { dg-message "opened here as write-only" } */
+ if (fd >= 0) /* { dg-message "assuming 'fd' is a valid file descriptor \\(>= 0\\)" "event1" } */
+ /* { dg-message "following 'true' branch \\(when 'fd >= 0'\\)..." "event2" { target *-*-* } .-1 } */
+ {
+ read (fd, buf, 1); /* { dg-warning "'read' on 'write-only' file descriptor 'fd'" "warning" } */
+ /* { dg-message "\\(4\\) ...to here" "event1" { target *-*-* } .-1 } */
+ /* { dg-message "\\(5\\) 'read' on 'write-only' file descriptor 'fd'" "event2" { target *-*-* } .-2 } */
+ close (fd);
+ }
+}
+
+
+void
+test_3 (const char *path, void *buf)
+{
+ int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+ if (fd >= 0)
+ {
+ close(fd); /* {dg-message "\\(2\\) closed here"} */
+ read(fd, buf, 1); /* { dg-warning "'read' on closed file descriptor 'fd'" } */
+ /* {dg-message "\\(3\\) 'read' on closed file descriptor 'fd' here" "" {target *-*-*} .-1 } */
+ }
+}
+
+void
+test_4 (const char *path, void *buf)
+{
+ int fd = open (path, O_RDWR); /* { dg-message "\\(1\\) opened here" } */
+ if (fd >= 0)
+ {
+ close(fd); /* {dg-message "\\(2\\) closed here"} */
+ write(fd, buf, 1); /* { dg-warning "'write' on closed file descriptor 'fd'" } */
+ /* {dg-message "\\(3\\) 'write' on closed file descriptor 'fd' here" "" {target *-*-*} .-1 } */
+ }
+}
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2022-07-02 16:45 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-02 16:45 [gcc r13-1404] analyzer: implement five new warnings for misuse of POSIX file descriptor APIs [PR106003] Immad Mir
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).