public inbox for gcc@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] PR 106003
@ 2022-07-02 14:04 Mir Immad
  2022-07-02 14:05 ` Mir Immad
  2022-07-02 15:32 ` David Malcolm
  0 siblings, 2 replies; 5+ messages in thread
From: Mir Immad @ 2022-07-02 14:04 UTC (permalink / raw)
  To: gcc, David Malcolm

From 62b7b7736975172f03b30783436fbc9217324223 Mon Sep 17 00:00:00 2001
From: mir <mirimmad17@gmail.com>
Date: Sat, 2 Jul 2022 15:04:37 +0530
Subject: [PATCH] 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.
---
 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(+)
 create mode 100644 gcc/analyzer/sm-fd.cc
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-4.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index b6dcc45a58a..04631f737ea 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1269,6 +1269,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 23dfc797cea..479990b0e5e 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 174bc09e5cf..b4a46dddfbb 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -9709,6 +9709,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
@@ -9783,6 +9788,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 } */
+    }
+}
-- 
2.25.1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] PR 106003
  2022-07-02 14:04 [PATCH] PR 106003 Mir Immad
@ 2022-07-02 14:05 ` Mir Immad
  2022-07-02 15:32 ` David Malcolm
  1 sibling, 0 replies; 5+ messages in thread
From: Mir Immad @ 2022-07-02 14:05 UTC (permalink / raw)
  To: gcc, David Malcolm

Hi everyone,

This is a patch for PR 106003
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106003; tested on x86_64.

On Sat, Jul 2, 2022 at 7:34 PM Mir Immad <mirimnan017@gmail.com> wrote:

> From 62b7b7736975172f03b30783436fbc9217324223 Mon Sep 17 00:00:00 2001
> From: mir <mirimmad17@gmail.com>
> Date: Sat, 2 Jul 2022 15:04:37 +0530
> Subject: [PATCH] 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.
> ---
>  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(+)
>  create mode 100644 gcc/analyzer/sm-fd.cc
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-2.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-3.c
>  create mode 100644 gcc/testsuite/gcc.dg/analyzer/fd-4.c
>
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index b6dcc45a58a..04631f737ea 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -1269,6 +1269,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 23dfc797cea..479990b0e5e 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 174bc09e5cf..b4a46dddfbb 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -9709,6 +9709,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
> @@ -9783,6 +9788,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 } */
> +    }
> +}
> --
> 2.25.1
>
>
>

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] PR 106003
  2022-07-02 14:04 [PATCH] PR 106003 Mir Immad
  2022-07-02 14:05 ` Mir Immad
@ 2022-07-02 15:32 ` David Malcolm
  2022-07-03  9:06   ` Mir Immad
  1 sibling, 1 reply; 5+ messages in thread
From: David Malcolm @ 2022-07-02 15:32 UTC (permalink / raw)
  To: Mir Immad, gcc

On Sat, 2022-07-02 at 19:34 +0530, Mir Immad wrote:
> From 62b7b7736975172f03b30783436fbc9217324223 Mon Sep 17 00:00:00 2001
> From: mir <mirimmad17@gmail.com>
> Date: Sat, 2 Jul 2022 15:04:37 +0530
> Subject: [PATCH] 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.

[...snip...]

Hi Immad.

Thanks for the updated patch.

For everyone else, we've been discussing this patch off-list.  We've
had some issues with gmail mangling patches; FWIW a pristine version of
the patch can be seen at:
  https://mirimmad.github.io/patch-02-07.txt

As discussed off-list, you've successfully bootstrapped this patch and
run the testsuite without regressions (and a bunch of extra PASSes), so
this patch is ready for you to push it to the "master" git branch (aka
trunk).  Please go ahead with that (or let me know if you need help
[1]).

Note that Tim's first analyzer patch is also ready to push, so there's
a chance that your patches might conflict with each other (though I
think you're touching different areas of the analyzer, so I'm hoping
that won't happen).

There's plenty of scope for followups, such as adding attributes for
parameters that expect an open file-descriptor, or for handling socket
APIs, etc.  Also, Murphy's Law means that there's sure to be at least
something we missed in review :/

Let's move followup patches to the gcc-patches mailing list, rather
than the "gcc" list.

Thanks
Dave

[1] though I'll only be checking email intermittently this weekend and
on Monday (which is a holiday here in the USA).


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] PR 106003
  2022-07-02 15:32 ` David Malcolm
@ 2022-07-03  9:06   ` Mir Immad
  2022-07-03 19:30     ` David Malcolm
  0 siblings, 1 reply; 5+ messages in thread
From: Mir Immad @ 2022-07-03  9:06 UTC (permalink / raw)
  To: David Malcolm, gcc

Thank you.
I've committed the patch, and it is covered by the  Developer Certificate
of Origin (DCO).

Immad.

On Sat, Jul 2, 2022 at 9:02 PM David Malcolm <dmalcolm@redhat.com> wrote:

> On Sat, 2022-07-02 at 19:34 +0530, Mir Immad wrote:
> > From 62b7b7736975172f03b30783436fbc9217324223 Mon Sep 17 00:00:00 2001
> > From: mir <mirimmad17@gmail.com>
> > Date: Sat, 2 Jul 2022 15:04:37 +0530
> > Subject: [PATCH] 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.
>
> [...snip...]
>
> Hi Immad.
>
> Thanks for the updated patch.
>
> For everyone else, we've been discussing this patch off-list.  We've
> had some issues with gmail mangling patches; FWIW a pristine version of
> the patch can be seen at:
>   https://mirimmad.github.io/patch-02-07.txt
>
> As discussed off-list, you've successfully bootstrapped this patch and
> run the testsuite without regressions (and a bunch of extra PASSes), so
> this patch is ready for you to push it to the "master" git branch (aka
> trunk).  Please go ahead with that (or let me know if you need help
> [1]).
>
> Note that Tim's first analyzer patch is also ready to push, so there's
> a chance that your patches might conflict with each other (though I
> think you're touching different areas of the analyzer, so I'm hoping
> that won't happen).
>
> There's plenty of scope for followups, such as adding attributes for
> parameters that expect an open file-descriptor, or for handling socket
> APIs, etc.  Also, Murphy's Law means that there's sure to be at least
> something we missed in review :/
>
> Let's move followup patches to the gcc-patches mailing list, rather
> than the "gcc" list.
>
> Thanks
> Dave
>
> [1] though I'll only be checking email intermittently this weekend and
> on Monday (which is a holiday here in the USA).
>
>

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] PR 106003
  2022-07-03  9:06   ` Mir Immad
@ 2022-07-03 19:30     ` David Malcolm
  0 siblings, 0 replies; 5+ messages in thread
From: David Malcolm @ 2022-07-03 19:30 UTC (permalink / raw)
  To: Mir Immad, gcc

On Sun, 2022-07-03 at 14:36 +0530, Mir Immad wrote:
> Thank you.
> I've committed the patch, and it is covered by the  Developer
> Certificate
> of Origin (DCO).

Excellent - thanks.

Congratulations on getting your first patch into GCC!

Dave



^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2022-07-03 19:30 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-02 14:04 [PATCH] PR 106003 Mir Immad
2022-07-02 14:05 ` Mir Immad
2022-07-02 15:32 ` David Malcolm
2022-07-03  9:06   ` Mir Immad
2022-07-03 19:30     ` David Malcolm

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).