From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-pl1-x630.google.com (mail-pl1-x630.google.com [IPv6:2607:f8b0:4864:20::630]) by sourceware.org (Postfix) with ESMTPS id CACDF3858C51 for ; Tue, 21 Jun 2022 16:30:25 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org CACDF3858C51 Received: by mail-pl1-x630.google.com with SMTP id l6so4833764plg.11 for ; Tue, 21 Jun 2022 09:30:25 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=O9TnH/CyYqBBsBejmbINrD7vUABhQZ3H5iDV+jcKXv0=; b=TIVPF2f8509x7rL/H0ejpqkS50rNZB5lOHRz7q74ATfFtc3vqmV2GZtg9nSbZPFZLt Ahkrk23n7E+DXQz26tplzLBsscU1XE8rYc1539ryI8ocoBnJLTRg7dH4uZGlQJ1oVYPy Ge4t9dAoA2k5EPRrarucvX4ewS13rY1/7Bq5eLo3m08fgZmKCjosZIYMUwcOZPNoYtlo tPC02YbzmMCItmM6XFwBQyXB/36FmpV4sVEyunteZqOJg3tdIZEzrDeQuowF91GSq/9s iMMIJDSiRWvmCl8St8uomSndgyx7ci+0AqWyCXr+JIwbNyLgOvZM+G38QK1wuUMkKuWh 2RmQ== X-Gm-Message-State: AJIora9eKfTv8z7lh2ERvUcDpl5+ZCbUCdJd+5grmkFS+WlYejXyBIMC mvWMPuZEROrxpPUJmZuCVUhYXLf82adyN3jcBU8mwMiUAMo= X-Google-Smtp-Source: AGRyM1tnV5Q02vOlmjywQBbjaaxBFySMY3NVP+Yx/jo1vl5w8amo+v7OhYPKmPhuHGSZNLhDgBF+RZZlyYYMf8r06dA= X-Received: by 2002:a17:90b:2251:b0:1e6:76a8:44f3 with SMTP id hk17-20020a17090b225100b001e676a844f3mr45586212pjb.71.1655829023440; Tue, 21 Jun 2022 09:30:23 -0700 (PDT) MIME-Version: 1.0 From: Mir Immad Date: Tue, 21 Jun 2022 22:00:11 +0530 Message-ID: Subject: [PATCH] static analysis support for posix file desccriptor APIs To: gcc@gcc.gnu.org, David Malcolm X-Spam-Status: No, score=-7.2 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM, GIT_PATCH_0, HTML_MESSAGE, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org Content-Type: text/plain; charset="UTF-8" X-Content-Filtered-By: Mailman/MimeDel 2.1.29 X-BeenThere: gcc@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 21 Jun 2022 16:30:32 -0000 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..d99acfbb069 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -54,6 +54,10 @@ The minimum number of supernodes within a function for the analyzer to consider Common Joined UInteger Var(param_analyzer_max_enodes_for_full_dump) Init(200) Param The maximum depth of exploded nodes that should appear in a dot dump before switching to a less verbose format. +Wanalyzer-double-close +Common Var(warn_analyzer_double_close) Init(1) Warning +Warn about code paths in which a file descriptor can be closed more than once. + Wanalyzer-double-fclose Common Var(warn_analyzer_double_fclose) Init(1) Warning Warn about code paths in which a stdio FILE can be closed more than once. @@ -66,6 +70,10 @@ 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-file-descriptor-leak +Common Var(warn_analyzer_file_descriptor_leak) Init(1) Warning +Warn about code paths in which a file descriptor is not closed. + Wanalyzer-file-leak Common Var(warn_analyzer_file_leak) Init(1) Warning Warn about code paths in which a stdio FILE is not closed. @@ -82,6 +90,14 @@ Wanalyzer-mismatching-deallocation Common Var(warn_analyzer_mismatching_deallocation) Init(1) Warning Warn about code paths in which the wrong deallocation function is called. +Wanalyzer-mismatching-operation-on-file-descriptor +Common Var(warn_analyzer_mismatching_operation_on_file_descriptor) Init(1) Warning +Warn about the code paths in which read on write-only file descriptor or write on read-only file descriptor is called. + +Wanalyzer-possible-invalid-file-descriptor +Common Var(warn_analyzer_possible_invalid_file_descriptor) Init(1) Warning +warn about code paths in which a possibly invalid file descriptor is passed to a must-be-a-valid file descriptor function argument. + Wanalyzer-possible-null-argument Common Var(warn_analyzer_possible_null_argument) Init(1) Warning Warn about code paths in which a possibly-NULL value is passed to a must-not-be-NULL function argument. @@ -90,6 +106,10 @@ Wanalyzer-possible-null-dereference Common Var(warn_analyzer_possible_null_dereference) Init(1) Warning Warn about code paths in which a possibly-NULL pointer is dereferenced. +Wanalyzer-read-write-on-closed-file-descriptor +Common Var(warn_analyzer_read_write_on_closed_file_descriptor) Init(1) Warning +Warn about code paths in which a read on write in performed on a closed file descriptor. + Wanalyzer-unsafe-call-within-signal-handler Common Var(warn_analyzer_unsafe_call_within_signal_handler) Init(1) Warning Warn about code paths in which an async-signal-unsafe function is called from a signal handler. diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc new file mode 100644 index 00000000000..23e79e3e16a --- /dev/null +++ b/gcc/analyzer/sm-fd.cc @@ -0,0 +1,697 @@ +/* FIXME: add copyright header. */ + +#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" + +#include +#if ENABLE_ANALYZER + +namespace ana +{ + +namespace +{ +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 ()) + { + int val = TREE_INT_CST_LOW (cst); + if (val < 0) + { + return m_null; + } + } + 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 (state_t s) const; + bool is_valid (state_t s) const; + + /* State for a file descriptor that hasn't been checked for validity. */ + state_t m_unchecked_read_write; + + /* State for a file descriptor opened with O_RDONLY flag. */ + state_t m_unchecked_read_only; + + /* State for a file descriptor opneded with O_WRONLY flag. */ + state_t m_unchecked_write_only; + + /* State for a file descriptor that is known to be invalid. */ + state_t m_null; + + /* State for a file descriptor that was opened in read-write mode and is known + * to be valid. */ + state_t m_valid_read_write; + + /* State for a file descriptor that was opened as read-only and is known to be + * valid*/ + state_t m_valid_read_only; + + /* State for a file descriptor that was opened as write-only and is known to + * be valid*/ + state_t m_valid_write_only; + + /* 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; + void on_write (sm_context *sm_ctxt, const supernode *node, const gimple *stmt, + const gcall *call) 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 (change.m_new_state)) + { + if (change.m_new_state == m_sm.m_unchecked_read_write) + return change.formatted_print ("opened here as %"); + + if (change.m_new_state == m_sm.m_unchecked_read_only) + return change.formatted_print ("opened here as %"); + + if (change.m_new_state == m_sm.m_unchecked_write_only) + return change.formatted_print ("opened here as %"); + } + + if (m_sm.is_unchecked (change.m_old_state) + && m_sm.is_valid (change.m_new_state)) + if (change.m_expr) + return change.formatted_print ( + "assuming %qE is a valid file descriptor", change.m_expr); + else + return change.formatted_print ("assuming a valid file descriptor"); + + if (m_sm.is_unchecked (change.m_old_state) + && change.m_new_state == m_sm.m_null) + if (change.m_expr) + return change.formatted_print ( + "assuming %qE is an invalid file descriptor", 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_file_descriptor_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 (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 read_write_diag_fd : public fd_diagnostic +{ +public: + /* mode specfies whether read on write was attempted or vice versa. + */ + read_write_diag_fd (const fd_state_machine &sm, tree arg, int mode) + : m_mode (mode), fd_diagnostic (sm, arg) + { + } + + const char * + get_kind () const final override + { + return "read_write_diag_fd"; + } + + + int + get_controlling_option () const final override + { + return OPT_Wanalyzer_mismatching_operation_on_file_descriptor; + } + + bool + emit (rich_location *rich_loc) final override + { + if (m_mode) + { + return warning_at (rich_loc, get_controlling_option (), + "% on % file descriptor %qE", + m_arg); + } + else + { + return warning_at (rich_loc, get_controlling_option (), + "% on % file descriptor %qE", + m_arg); + } + } + + label_text + describe_state_change (const evdesc::state_change &change) override + { + return fd_diagnostic::describe_state_change (change); + } + + label_text + describe_final_event (const evdesc::final_event &ev) final override + { + if (m_mode) + return ev.formatted_print ("%qs on % file descriptor %qE", + "write", m_arg); + else + return ev.formatted_print ("%qs on % file descriptor %qE", + "read", m_arg); + } + +private: + int m_mode; +}; + +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_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 % of file descriptor %qE", m_arg); + } + + label_text + describe_state_change (const evdesc::state_change &change) override + { + if (m_sm.is_unchecked (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 read_write_on_closed_fd : public fd_diagnostic +{ +public: + read_write_on_closed_fd (const fd_state_machine &sm, tree arg, int mode) + : fd_diagnostic (sm, arg), m_mode (mode) + { + } + + const char * + get_kind () const final override + { + if (m_mode) + return "write_on_closed_fd"; + else + return "read_on_closed_fd"; + } + + + int + get_controlling_option () const final override + { + return OPT_Wanalyzer_read_write_on_closed_file_descriptor; + } + + bool + emit (rich_location *rich_loc) final override + { + if (m_mode) + return warning_at (rich_loc, get_controlling_option (), + "% on closed file descriptor %qE", m_arg); + else + return warning_at (rich_loc, get_controlling_option (), + "% on closed file descriptor %qE", m_arg); + } + + label_text + describe_state_change (const evdesc::state_change &change) override + { + if (m_sm.is_unchecked (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 + { + if (m_mode) + return ev.formatted_print ("% on closed file descriptor %qE here", + m_arg); + else + return ev.formatted_print ("% on closed file descriptor %qE here", + m_arg); + } + +private: + int m_mode; +}; + +class possible_invalid_fd_diag : public fd_diagnostic +{ +public: + possible_invalid_fd_diag (const fd_state_machine &sm, tree arg, int mode) + : fd_diagnostic (sm, arg), m_mode (mode) + { + } + + const char * + get_kind () const final override + { + return "possible_invalid_fd_diag"; + } + + + int + get_controlling_option () const final override + { + return OPT_Wanalyzer_possible_invalid_file_descriptor; + } + + bool + emit (rich_location *rich_loc) final override + { + if (m_mode) + return warning_at (rich_loc, get_controlling_option (), + "% on possibly invalid file descriptor %qE", + m_arg); + else + return warning_at (rich_loc, get_controlling_option (), + "% on possibly invalid file descriptor %qE", + m_arg); + } + + label_text + describe_state_change (const evdesc::state_change &change) override + { + if (m_sm.is_unchecked (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 + { + return ev.formatted_print ("%qE could be invalid: unchecked value from %@", + m_arg, &m_first_open_event); + } + +private: + diagnostic_event_id_t m_first_open_event; + int m_mode; +}; + +fd_state_machine::fd_state_machine (logger *logger) + : state_machine ("file-descriptor", logger), + 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_null (add_state ("fd-null")), m_closed (add_state ("fd-closed")), + 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_stop (add_state ("fd-stop")) +{ +} + +bool +fd_state_machine::is_unchecked (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 (state_t s) const +{ + return s == m_valid_read_write || s == m_valid_read_only + || s == m_valid_write_only; +} + +bool +fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node, + const gimple *stmt) const +{ + if (const gcall *call = dyn_cast (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); + return true; + } // "write" + + if (is_named_call_p (callee_fndecl, "read", call, 3)) + { + on_read (sm_ctxt, node, stmt, call); + return true; + } // "read" + + return true; + } + + 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); + /* 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_RDONLY) + sm_ctxt->on_transition (node, stmt, lhs, m_start, + m_unchecked_read_only); + else if (flag == O_WRONLY) + sm_ctxt->on_transition (node, stmt, lhs, m_start, + m_unchecked_write_only); + else + sm_ctxt->on_transition (node, stmt, lhs, m_start, + m_unchecked_read_write); + } + } + else + { + /* FIXME: add leak reporting */ + } +} + +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); + + 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_null, m_closed); + + if (sm_ctxt->get_state (stmt, arg) == m_closed) + { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + 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 arg = gimple_call_arg (call, 0); + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + + if (sm_ctxt->get_state (stmt, arg) == m_closed) + { + sm_ctxt->warn (node, stmt, arg, new read_write_on_closed_fd (*this, diag_arg, 0)); + } + + if (!is_valid (sm_ctxt->get_state (stmt, arg))) + { + sm_ctxt->warn (node, stmt, arg, + new possible_invalid_fd_diag (*this, diag_arg, 0)); + } + + if (sm_ctxt->get_state (stmt, arg) == m_unchecked_write_only + || sm_ctxt->get_state (stmt, arg) == m_valid_write_only) + { + sm_ctxt->warn (node, stmt, arg, + new read_write_diag_fd (*this, diag_arg, 0)); + } +} +void +fd_state_machine::on_write (sm_context *sm_ctxt, const supernode *node, + const gimple *stmt, const gcall *call) const +{ + tree arg = gimple_call_arg (call, 0); + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + + if (sm_ctxt->get_state (stmt, arg) == m_closed) + { + sm_ctxt->warn (node, stmt, arg, new read_write_diag_fd (*this, diag_arg, 1)); + } + if (!is_valid (sm_ctxt->get_state (stmt, arg))) + { + sm_ctxt->warn (node, stmt, arg, + new possible_invalid_fd_diag (*this, diag_arg, 1)); + } + + if (sm_ctxt->get_state (stmt, arg) == m_unchecked_read_only + || sm_ctxt->get_state (stmt, arg) == m_valid_read_only) + { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + sm_ctxt->warn (node, stmt, arg, + new read_write_diag_fd (*this, diag_arg, 1)); + } +} + +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 (!rhs->all_zeroes_p ()) + return; + + if (op == GE_EXPR) + { + 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); + } + else if (op == LT_EXPR) + { + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_write, m_null); + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_read_only, m_null); + sm_ctxt->on_transition (node, stmt, lhs, m_unchecked_write_only, m_null); + } +} + +bool +fd_state_machine::can_purge_p (state_t s) const +{ + if (is_unchecked (s) || is_valid (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 &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/testsuite/gcc.dg/analyzer/fd-1.c b/gcc/testsuite/gcc.dg/analyzer/fd-1.c new file mode 100644 index 00000000000..985e9ac75de --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-1.c @@ -0,0 +1,26 @@ +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 } */ + } +} + 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..4ff4b46b42b --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-2.c @@ -0,0 +1,27 @@ +int open(const char *, int mode); +void close(int fd); +#define O_RDONLY 0 +#define O_WRONLY 1 +#define O_RDWR 2 + +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" "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\\) seconf 'close' here; first was at \\(5\\)" "" { target *-*-* } .-1 } */ +} \ 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..d32e3fe4923 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-3.c @@ -0,0 +1,27 @@ +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_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); +} 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..a802c4b6d42 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-4.c @@ -0,0 +1,37 @@ +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" "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" "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); + } +} \ No newline at end of file