From 9c232fefdf18caf1bb9c84acfb108fbbd074ee98 Mon Sep 17 00:00:00 2001 From: Ridham Khurana Date: Wed, 18 Mar 2026 10:44:25 -0400 Subject: [PATCH 20/38] analyzer: model getenv Model getenv as a known function so that the analyzer bifurcates on its return value, handling both the NULL and non-NULL cases. Also check that its argument is a null-terminated string. gcc/analyzer/ChangeLog: * kf.cc (class kf_getenv): New. (kf_getenv::impl_call_post): New. (register_known_functions): Register kf_getenv. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/getenv-1.c: New test. Signed-off-by: Ridham Khurana --- gcc/analyzer/kf.cc | 89 ++++++++++++++++++++++++ gcc/testsuite/gcc.dg/analyzer/getenv-1.c | 49 +++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 gcc/testsuite/gcc.dg/analyzer/getenv-1.c diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc index a7d5b90d3a4..b0008ecb0b0 100644 --- a/gcc/analyzer/kf.cc +++ b/gcc/analyzer/kf.cc @@ -1295,6 +1295,94 @@ public: } }; +/* Handler for calls to "getenv". + char *getenv (const char *name); + + Returns either NULL (if the environment variable is not found), + or a pointer to the value string. */ + +class kf_getenv : public known_function +{ +public: + bool matches_call_types_p (const call_details &cd) const final override + { + return (cd.num_args () == 1 && cd.arg_is_pointer_p (0)); + } + + void impl_call_pre (const call_details &cd) const final override + { + cd.check_for_null_terminated_string_arg (0); + } + + void impl_call_post (const call_details &cd) const final override; +}; + +void +kf_getenv::impl_call_post (const call_details &cd) const +{ + class getenv_call_info : public call_info + { + public: + getenv_call_info (const call_details &cd, bool found) + : call_info (cd), m_found (found) + { + } + + void print_desc (pretty_printer &pp) const final override + { + if (m_found) + pp_printf (&pp, + "when %qE returns non-NULL", + get_fndecl ()); + else + pp_printf (&pp, + "when %qE returns NULL", + get_fndecl ()); + } + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const final override + { + const call_details cd (get_call_details (model, ctxt)); + if (tree lhs_type = cd.get_lhs_type ()) + { + region_model_manager *mgr = model->get_manager (); + const svalue *result; + if (m_found) + { + /* Return a conjured non-NULL pointer. */ + result + = mgr->get_or_create_conjured_svalue (lhs_type, + &cd.get_call_stmt (), + cd.get_lhs_region (), + conjured_purge (model, + ctxt)); + const svalue *null_ptr + = mgr->get_or_create_int_cst (lhs_type, 0); + model->add_constraint (result, NE_EXPR, null_ptr, ctxt); + } + else + result = mgr->get_or_create_int_cst (lhs_type, 0); + cd.maybe_set_lhs (result); + } + return true; + } + private: + bool m_found; + }; + + /* Body of kf_getenv::impl_call_post. */ + if (cd.get_ctxt ()) + { + cd.get_ctxt ()->bifurcate + (std::make_unique (cd, false)); + cd.get_ctxt ()->bifurcate + (std::make_unique (cd, true)); + cd.get_ctxt ()->terminate_path (); + } +} + /* Handler for calls to "putenv". In theory we could try to model the state of the environment variables @@ -2812,6 +2900,7 @@ register_known_functions (known_function_manager &kfm, /* Known POSIX functions, and some non-standard extensions. */ { kfm.add ("fopen", std::make_unique ()); + kfm.add ("getenv", std::make_unique ()); kfm.add ("mkdtemp", std::make_unique ()); kfm.add ("mkostemp", std::make_unique ()); kfm.add ("mkostemps", std::make_unique ()); diff --git a/gcc/testsuite/gcc.dg/analyzer/getenv-1.c b/gcc/testsuite/gcc.dg/analyzer/getenv-1.c new file mode 100644 index 00000000000..43afdf1f115 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/getenv-1.c @@ -0,0 +1,49 @@ +#include +#include "analyzer-decls.h" + +extern void do_something (const char *p) + __attribute__((nonnull (1))); + +/* Verify that the analyzer bifurcates on getenv, considering + both NULL and non-NULL outcomes. */ + +void test_null_deref (void) +{ + char *p = getenv ("HOME"); /* { dg-message "when 'getenv' returns NULL" } */ + *p = 'a'; /* { dg-warning "dereference of NULL 'p'" } */ +} + +void test_checked (void) +{ + char *p = getenv ("HOME"); + if (p) + do_something (p); /* no warning: p is non-NULL here. */ +} + +void test_unchecked_use (void) +{ + char *p = getenv ("PATH"); /* { dg-message "when 'getenv' returns NULL" } */ + do_something (p); /* { dg-warning "use of NULL 'p' where non-null expected" } */ +} + +void test_bifurcation (void) +{ + char *p = getenv ("EDITOR"); + if (p) + __analyzer_eval (p != NULL); /* { dg-warning "TRUE" } */ +} + +void test_getenv_returns_nonnull (void) +{ + char *p = getenv ("TERM"); + if (!p) + return; + __analyzer_eval (p != NULL); /* { dg-warning "TRUE" } */ +} + +void test_unterminated (void) +{ + char buf[3] = "abc"; + getenv (buf); /* { dg-warning "stack-based buffer over-read" } */ + /* { dg-message "while looking for null terminator for argument 1 \\('&buf'\\) of 'getenv'..." "event" { target *-*-* } .-1 } */ +} -- 2.49.0