/* -*- Mode: C++; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * Tartan * Copyright © 2014 Collabora Ltd. * * Tartan 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 of the License, or * (at your option) any later version. * * Tartan 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 Tartan. If not, see . * * Authors: * Philip Withnall */ /** * GErrorChecker: * * This is a checker for #GError usage, both with the g_error_*() API, and with * normal C pointer operations on #GErrors. It validates that all #GError * pointers are initialised to %NULL, that valid #GErrors are not overwritten, * and that #GErrors are not double-freed or leaked. It also validates more * mundane things, like whether error codes actually belong in the domain passed * to g_error_new() (for example). * * This is effectively a highly specific memory allocation checker, imposing the * rules about clearing #GError pointers to %NULL which #GError convention * dictates. * * The checker uses full path-dependent analysis, so will catch bugs arising * from #GErrors being handled differently on different control paths, which is * empirically where most #GError bugs arise. * * The checker is implemented using a combination of Clang’s internal symbolic * value model, and a custom ErrorMap using Clang’s program state maps. The * ErrorMap tracks state for each GError* pointer it knows about, using three * states: * • Clear: error = NULL * • Set: error ≠ NULL ∧ valid_allocation(error) * • Freed: error ≠ NULL ∧ ¬valid_allocation(error) * * In the comments below, the following modelling functions are used: * valid_allocation(error): * True iff error has been allocated as a GError, but not yet freed. * Corresponds to the ErrorState.Set state. * error_codes(domain): * Returns a set of error codes which are valid for the given domain, * as defined by the enum associated with that error domain. * * FIXME: Future work could be to implement: * • Support for user-defined functions which take GError** parameters. * • Add support for g_error_copy() * • Add support for g_error_matches() * • Add support for g_prefix_error() * • Implement check::DeadSymbols (for cleaning up internal state) * • Implement check::PointerEscape (for leaks) * • Implement check::ConstPointerEscape (for leaks) * • Implement check::PreStmt (for leaks) * • Implement check::PostStmt (for leaks) * • Implement check::Location (for bad dereferences) * • Implement eval::Assume * • Check that error codes match their domains. * • Set the MemRegion contents more explicitly in _gerror_new() — it would be * nice to get static analysis on code and domain values. * • Domain analysis on propagated GErrors: track which error domains each * function can possibly return, and warn if they’re not all handled by * callers. */ #include #include #include #include #include "gerror-checker.h" #include "type-manager.h" #include "debug.h" namespace tartan { using namespace clang; struct ErrorState { enum Kind { Clear, Set, Freed } K; SourceRange S; ErrorState (Kind k, const SourceRange &s) : K (k), S (s) {} bool isClear () const { return K == Clear; } bool isSet () const { return K == Set; } bool isFreed () const { return K == Freed; } bool operator== (const ErrorState &X) const { return K == X.K && S == X.S; } static ErrorState getClear (const SourceRange &s) { return ErrorState (Clear, s); } static ErrorState getSet (const SourceRange &s) { return ErrorState (Set, s); } static ErrorState getFreed (const SourceRange &s) { return ErrorState (Freed, s); } void Profile (llvm::FoldingSetNodeID &ID) const { ID.AddInteger (K); ID.AddPointer (&S); } void dump (raw_ostream &stream) const { switch (K) { case Clear: stream << "Clear"; break; case Set: stream << "Set"; break; case Freed: stream << "Freed"; break; default: g_assert_not_reached (); } } }; } /* namespace tartan */ /* Track GError*s and their states in a map stored on the ProgramState. * The namespacing is necessary to be able to specialise a Clang template. */ REGISTER_MAP_WITH_PROGRAMSTATE (ErrorMap, clang::ento::SymbolRef, tartan::ErrorState) namespace tartan { static ProgramStateRef _error_map_remove (ProgramStateRef state, SymbolRef symbol) { DEBUG ("error_map_remove: " << symbol); return state->remove (symbol); } static ProgramStateRef _error_map_set (ProgramStateRef state, SymbolRef symbol, ErrorState error_state) { DEBUG ("error_map_set: " << symbol); return state->set (symbol, error_state); } static const ErrorState * _error_map_get (ProgramStateRef state, SymbolRef symbol) { DEBUG ("error_map_get: " << symbol); return state->get (symbol); } /* Try to get the SVal for the GError* pointed to by a GError** SVal. */ SVal GErrorChecker::_error_from_error_ptr (SVal ptr_error_location, CheckerContext &context) const { DEBUG_DUMPABLE ("Getting GError* location from call:", ptr_error_location); return context.getState()->getSVal(ptr_error_location.castAs()); } /** * Just before a g_set_error(error_ptr, domain, code, format, …) call, check * that: * (error_ptr = NULL) ∨ (*error_ptr = NULL) * code ϵ error_codes(domain) */ ProgramStateRef GErrorChecker::_handle_pre_g_set_error (CheckerContext &context, const CallEvent &call_event) const { if (!this->_assert_gerror_ptr_clear (call_event.getArgSVal (0), context.getState (), context, call_event.getArgSourceRange (0)) || !this->_assert_code_in_domain (call_event.getArgSVal (1), call_event.getArgSVal (2), context.getState (), context, call_event.getArgSourceRange (1), call_event.getArgSourceRange (2))) { return NULL; } return context.getState (); } /** * Just after a g_set_error(error_ptr, …) call, change the state to: * • Conjure a new heap memory region for a new GError. * • Bind that to (*error_ptr). * • Update the ErrorMap to mark (*error_ptr) as Set. */ ProgramStateRef GErrorChecker::_handle_eval_g_set_error (CheckerContext &context, const CallExpr &call_expr) const { ProgramStateRef state = context.getState (); /* Statically construct a new GError instance and bind it to the * dereferenced GError** pointer. */ DefinedSVal *allocated_sval = NULL; state = this->_gerror_new (&call_expr, false, &allocated_sval, state, context, call_expr.getSourceRange ()); SVal ptr_error_location = state->getSVal (call_expr.getArg (0), context.getLocationContext ()); state = this->_set_gerror (ptr_error_location, *allocated_sval, state, context, call_expr.getArg (0)->getSourceRange ()); delete allocated_sval; return state; } /** * Just before a g_error_new(domain, code, format, …) call, check * that: * code ϵ error_codes(domain) */ ProgramStateRef GErrorChecker::_handle_pre_g_error_new (CheckerContext &context, const CallEvent &call_event) const { if (!this->_assert_code_in_domain (call_event.getArgSVal (0), call_event.getArgSVal (1), context.getState (), context, call_event.getArgSourceRange (0), call_event.getArgSourceRange (1))) { return NULL; } return context.getState (); } /** * Just after a g_error_new(…) call, change the state to: * • Conjure a new heap memory region for a new GError. * • Bind it to the call’s return value. */ ProgramStateRef GErrorChecker::_handle_eval_g_error_new (CheckerContext &context, const CallExpr &call_expr) const { return this->_gerror_new (&call_expr, true, NULL, context.getState (), context, call_expr.getSourceRange ()); } /** * Just before a g_error_free(error) call, check * that: * error ≠ NULL ∧ valid_allocation(error) */ ProgramStateRef GErrorChecker::_handle_pre_g_error_free (CheckerContext &context, const CallEvent &call_event) const { SVal error_location = call_event.getArgSVal (0); if (!this->_assert_gerror_set (error_location, false, context.getState (), context, call_event.getArgSourceRange (0))) { return NULL; } return context.getState (); } /** * Just after a g_error_free(error) call, change the state to: * • Update the ErrorMap to mark error as Free. * • Update the MemRegion for (*error) to fill it with undefined values. */ ProgramStateRef GErrorChecker::_handle_eval_g_error_free (CheckerContext &context, const CallExpr &call_expr) const { ProgramStateRef state = context.getState (); SVal error_location = state->getSVal (call_expr.getArg (0), context.getLocationContext ()); DEBUG_DUMPABLE ("Handle post-g_error_free:", error_location); state = this->_gerror_free (error_location, context.getState (), context, call_expr.getArg (0)->getSourceRange ()); return state; } /** * Just before a g_clear_error(error_ptr) call, check that: * error_ptr = NULL ∨ (*error_ptr) = NULL ∨ valid_allocation(*error_ptr) */ ProgramStateRef GErrorChecker::_handle_pre_g_clear_error (CheckerContext &context, const CallEvent &call_event) const { ProgramStateRef state; ProgramStateRef not_null_state, null_state; /* for GError* */ ProgramStateRef ptr_not_null_state, ptr_null_state; /* for GError** */ state = context.getState (); SVal _ptr_error_location = call_event.getArgSVal (0); if (!_ptr_error_location.getAs ()) { return state; } DefinedOrUnknownSVal ptr_error_location = _ptr_error_location.castAs (); /* Branch on whether the GError** is NULL. If it is, we have nothing to * do. */ std::tie (ptr_not_null_state, ptr_null_state) = state->assume (ptr_error_location); if (ptr_null_state && !ptr_not_null_state) { /* Definitely NULL. */ return state; } SVal error_location = this->_error_from_error_ptr (call_event.getArgSVal (0), context); /* Check whether the GError* is free. */ if (!this->_assert_gerror_set (error_location, true, state, context, call_event.getArgSourceRange (0))) { return NULL; } return state; } /** * Just after a g_clear_error(error_ptr) call, change the state to: * • Update the ErrorMap to mark (*error_ptr) as Clear. * • Update the MemRegion for (**error_ptr) to fill it with undefined values. * • Bind (*error_ptr) to NULL. */ ProgramStateRef GErrorChecker::_handle_eval_g_clear_error (CheckerContext &context, const CallExpr &call_expr) const { ProgramStateRef state = context.getState (); SVal ptr_error_location = state->getSVal (call_expr.getArg (0), context.getLocationContext ()); SVal error_location = this->_error_from_error_ptr (ptr_error_location, context); DEBUG_DUMPABLE ("Handle post-g_clear_error:", error_location); /* Free the GError*. */ state = this->_gerror_free (error_location, state, context, call_expr.getArg (0)->getSourceRange ()); if (state == NULL) { return state; } /* Set it to NULL. */ state = this->_clear_gerror (ptr_error_location, state, context, call_expr.getArg (0)->getSourceRange ()); return state; } /** * Just before a g_propagate_error(dest_error_ptr, src_error) call, check that: * src_error ≠ NULL ∧ valid_allocation(src_error) * dest_error_ptr = NULL ∨ (*dest_error_ptr) = NULL */ ProgramStateRef GErrorChecker::_handle_pre_g_propagate_error (CheckerContext &context, const CallEvent &call_event) const { SVal dest_ptr_location = call_event.getArgSVal (0); SVal src_location = call_event.getArgSVal (1); if (!this->_assert_gerror_ptr_clear (dest_ptr_location, context.getState (), context, call_event.getArgSourceRange (0)) || !this->_assert_gerror_set (src_location, false, context.getState (), context, call_event.getArgSourceRange (1))) { return NULL; } return context.getState (); } /** * Just after a g_propagate_error(dest_error_ptr, src_error) call, change the * state to: * • If (dest_error_ptr = NULL), update the ErrorMap to mark src_error as Free * and update the MemRegion for (*src_error) to fill it with undefined * values. * • If (dest_error_ptr ≠ NULL), bind (*dest_error_ptr) to src_error. */ ProgramStateRef GErrorChecker::_handle_eval_g_propagate_error (CheckerContext &context, const CallExpr &call_expr) const { ProgramStateRef state = context.getState (); SVal dest_ptr_location = state->getSVal (call_expr.getArg (0), context.getLocationContext ()); DEBUG_CODE (SVal dest_location = this->_error_from_error_ptr (dest_ptr_location, context)); SVal src_location = state->getSVal (call_expr.getArg (1), context.getLocationContext ()); if (!src_location.getAs ()) { DEBUG ("Cannot get src location as DefinedSVal."); return state; } DEBUG_DUMPABLE ("Handle post-g_propagate_error: dest_ptr_location:", dest_ptr_location); DEBUG_DUMPABLE ("Handle post-g_propagate_error: src_location:", src_location); /* Branch on whether the GError** is NULL. If it is, the src error * should be freed. */ if (!dest_ptr_location.getAs ()) { return state; } ProgramStateRef not_null_state, null_state; std::tie (not_null_state, null_state) = state->assume (dest_ptr_location.castAs ()); if (null_state != NULL) { /* Potentially NULL, so free the @src error. */ null_state = this->_gerror_free (src_location, null_state, context, call_expr.getArg (1)->getSourceRange ()); } if (not_null_state != NULL) { /* Set the @dest error. Don’t know why we have to use @dest_ptr_location * here, but it seems to work. Obviously my understanding of SVals is * worse than I thought. */ not_null_state = this->_set_gerror (dest_ptr_location, src_location.castAs (), not_null_state, context, call_expr.getArg (0)->getSourceRange ()); } if (not_null_state != NULL && null_state != NULL) { context.addTransition (null_state); } return (not_null_state != NULL) ? not_null_state : null_state; } /* Dispatch pre-call events to the different per-function handlers. */ void GErrorChecker::checkPreCall (const CallEvent &call, CheckerContext &context) const { if (!call.isGlobalCFunction ()) { return; } ASTContext &ast_context = context.getASTContext (); if (!this->_initialise_identifiers (ast_context)) { return; } const IdentifierInfo *call_ident = call.getCalleeIdentifier (); ProgramStateRef new_state; if (call_ident == this->_identifier_g_set_error || call_ident == this->_identifier_g_set_error_literal) { new_state = this->_handle_pre_g_set_error (context, call); } else if (call_ident == this->_identifier_g_error_new || call_ident == this->_identifier_g_error_new_literal || call_ident == this->_identifier_g_error_new_valist) { new_state = this->_handle_pre_g_error_new (context, call); } else if (call_ident == this->_identifier_g_error_free) { new_state = this->_handle_pre_g_error_free (context, call); } else if (call_ident == this->_identifier_g_clear_error) { new_state = this->_handle_pre_g_clear_error (context, call); } else if (call_ident == this->_identifier_g_propagate_error || call_ident == this->_identifier_g_propagate_prefixed_error) { new_state = this->_handle_pre_g_propagate_error (context, call); } else { new_state = NULL; } if (new_state != NULL) { context.addTransition (new_state); } } /* Dispatch call-evaluation events to the different per-function handlers. * Return true iff the call was evaluated. */ bool GErrorChecker::evalCall ( #ifdef HAVE_LLVM_9_0 const CallEvent &call_event, #else const CallExpr *call, #endif CheckerContext &context) const { #ifdef HAVE_LLVM_9_0 const CallExpr *call = llvm::dyn_cast(call_event.getOriginExpr()); if (!call) return false; #endif const FunctionDecl *func_decl = context.getCalleeDecl (call); if (func_decl == NULL || func_decl->getKind() != Decl::Function || !CheckerContext::isCLibraryFunction (func_decl)) { return false; } ASTContext &ast_context = context.getASTContext (); if (!this->_initialise_identifiers (ast_context)) { return false; } const IdentifierInfo *call_ident = func_decl->getIdentifier (); ProgramStateRef new_state; if (call_ident == this->_identifier_g_set_error || call_ident == this->_identifier_g_set_error_literal) { new_state = this->_handle_eval_g_set_error (context, *call); } else if (call_ident == this->_identifier_g_error_new || call_ident == this->_identifier_g_error_new_literal || call_ident == this->_identifier_g_error_new_valist) { new_state = this->_handle_eval_g_error_new (context, *call); } else if (call_ident == this->_identifier_g_error_free) { new_state = this->_handle_eval_g_error_free (context, *call); } else if (call_ident == this->_identifier_g_clear_error) { new_state = this->_handle_eval_g_clear_error (context, *call); } else if (call_ident == this->_identifier_g_propagate_error || call_ident == this->_identifier_g_propagate_prefixed_error) { new_state = this->_handle_eval_g_propagate_error (context, *call); } else { new_state = NULL; } if (new_state != NULL) { context.addTransition (new_state); } return (new_state != NULL); } /** * Just before a value binding of (loc = val), check that: * val = NULL ∨ (val ≠ NULL ∧ valid_allocation(val)) * loc = NULL ∨ ¬valid_allocation(loc) */ void GErrorChecker::checkBind (SVal loc, SVal val, const Stmt *stmt, CheckerContext &context) const { ProgramStateRef new_state; /* We’re only interested in stores into GError*s. */ const TypedValueRegion *region = dyn_cast_or_null (loc.getAsRegion ()); if (region == NULL) { return; } const ASTContext &ast_context = context.getASTContext (); if (!this->_initialise_identifiers (ast_context)) { return; } QualType error_type = ast_context.getPointerType (this->_gerror_type); QualType value_type = region->getValueType (); if (!context.getASTContext ().hasSameType (error_type, value_type)) { return; } /* Check the preconditions on loc and val. */ ProgramStateRef state = context.getState (); SVal loc_region = state->getSVal (region); if (!this->_assert_gerror_unset (loc_region, true, context.getState (), context, stmt->getSourceRange ()) || !this->_assert_gerror_set (val, true, context.getState (), context, stmt->getSourceRange ())) { return; } /* Update the binding. */ ConditionTruthVal val_is_null = state->isNull (val); if (val_is_null.isConstrainedTrue ()) { DEBUG_DUMPABLE ("Check bind: clearing GError*:", loc); new_state = this->_clear_gerror (loc, state, context, stmt->getSourceRange ()); } else { DEBUG_DUMPABLE ("Check bind: setting GError*:", loc); new_state = this->_set_gerror (loc, val.castAs (), state, context, stmt->getSourceRange ()); } if (new_state != NULL) { context.addTransition (new_state); } } void GErrorChecker::checkDeadSymbols (SymbolReaper &symbol_reaper, CheckerContext &context) const { #ifndef HAVE_LLVM_8_0 if (!symbol_reaper.hasDeadSymbols ()) { return; } #endif ProgramStateRef state = context.getState (); /* Iterate through the ErrorMap and find any error symbols which are * dead. */ ErrorMapTy error_map = state->get (); for (ErrorMapTy::iterator i = error_map.begin (), e = error_map.end (); i != e; ++i) { if (symbol_reaper.isDead (i->first)) { state = _error_map_remove (state, i->first); } } } /* Conjure a new symbol to represent a newly allocated GError*. * @call_expr may be %NULL if no expression corresponds to the allocation. * If non-%NULL, @allocated_sval_out will be filled with the address of an * allocated DefinedSVal, which the caller must delete. */ ProgramStateRef GErrorChecker::_gerror_new (const Expr *call_expr, bool bind_to_call, DefinedSVal **allocated_sval_out, ProgramStateRef state, CheckerContext &context, const SourceRange &source_range) const { DEBUG ("Conjuring new GError* symbol."); /* Bind the return value to the symbolic value from the heap region. */ unsigned int count = context.blockCount (); SValBuilder &sval_builder = context.getSValBuilder (); const LocationContext *location_context = context.getPredecessor ()->getLocationContext (); const ASTContext &ast_context = context.getASTContext (); SymbolManager &symbol_manager = sval_builder.getSymbolManager (); MemRegionManager &memory_manager = sval_builder.getRegionManager (); QualType error_type = ast_context.getPointerType (this->_gerror_type); assert (Loc::isLocType (error_type)); assert (SymbolManager::canSymbolicate (error_type)); SymbolRef allocated_symbol = symbol_manager.conjureSymbol (call_expr, location_context, error_type, count); DefinedSVal *allocated_sval = new loc::MemRegionVal (memory_manager.getSymbolicHeapRegion (allocated_symbol)); /* Sanity check: the SVal needs to be usable as a key in the * ErrorMap. */ assert (allocated_sval->getAsSymbol () != NULL); if (bind_to_call) { state = state->BindExpr (call_expr, context.getLocationContext (), *allocated_sval); assert (state != NULL); } /* Fill the region with the initialization value. */ state = state->bindDefaultInitial (*allocated_sval, UndefinedVal (), context.getLocationContext ()); const MemRegion *allocated_region = allocated_sval->getAsRegion (); assert (allocated_region); /* Set the region’s extent to sizeof(GError). */ const SymbolicRegion *symbolic_allocated_region = dyn_cast_or_null (allocated_region); if (symbolic_allocated_region != NULL) { /* Should have been initialised on entry to the checker. */ assert (this->_initialise_identifiers (ast_context)); DefinedOrUnknownSVal extent = symbolic_allocated_region->getExtent (sval_builder); const uint64_t _gerror_size = ast_context.getTypeSize (this->_gerror_type); DefinedOrUnknownSVal gerror_size = sval_builder.makeIntVal (_gerror_size, ast_context.getSizeType ()); DefinedOrUnknownSVal extent_constraint = sval_builder.evalEQ (state, extent, gerror_size); state = state->assume (extent_constraint, true); assert (state); } /* Mark the GError* as Set. */ SymbolRef allocated_sym = allocated_sval->getAsSymbol (); assert (allocated_sym); state = _error_map_set (state, allocated_sym, ErrorState::getSet (source_range)); /* Clean up. */ if (allocated_sval_out != NULL) { *allocated_sval_out = allocated_sval; } else { delete allocated_sval; } return state; } /* Mark a GError* as freed (but still non-NULL). */ ProgramStateRef GErrorChecker::_gerror_free (SVal error_location, ProgramStateRef state, CheckerContext &context, const SourceRange &source_range) const { /* Fill the MemRegion with rubbish. */ if (error_location.getAs ()) { state = state->bindLoc (error_location.castAs (), UndefinedVal (), context.getLocationContext ()); assert (state != NULL); } /* Set the region’s state to Freed. */ SymbolRef error_sym = error_location.getAsSymbol (); if (error_sym == NULL) { return state; } return _error_map_set (state, error_sym, ErrorState::getFreed (source_range)); } /** * Check a GError* is non-NULL and allocated before freeing it. * * Formally, this checks the conditions: * null_allowed = (error_location = NULL ∨ * (error_location ≠ NULL ∧ valid_allocation(error_location))) * ¬null_allowed = (error_location ≠ NULL ∧ valid_allocation(error_location)) * * Returns: false on a bug, true otherwise */ bool GErrorChecker::_assert_gerror_set (SVal error_location, bool null_allowed, ProgramStateRef state, CheckerContext &context, const SourceRange &source_range) const { if (error_location.getAs ()) { ExplodedNode *error_node = context.generateErrorNode (state); this->_initialise_bug_reports (); auto R = llvm::make_unique (*this->_use_uninitialised, "Using uninitialized GError", error_node); #if 0 bugreporter::trackNullOrUndefValue (error_node, stmt, *R); #endif R->addRange (source_range); Debug::emit_bug_report (std::move (R), context); return false; } else if (!error_location.getAs ()) { return true; } ProgramStateRef not_null_state, null_state; /* Branch on whether the GError* is NULL. If it is, we have nothing to * do. If it isn’t, it must be a valid allocation. */ std::tie (not_null_state, null_state) = state->assume (error_location.castAs ()); if (null_state && !not_null_state && !null_allowed) { /* Definitely NULL. */ ExplodedNode *error_node = context.generateErrorNode (state); this->_initialise_bug_reports (); auto R = llvm::make_unique (*this->_free_cleared, "Freeing non-set GError", error_node); #if 0 bugreporter::trackNullOrUndefValue (error_node, stmt, *R); #endif R->addRange (source_range); Debug::emit_bug_report (std::move (R), context); return false; } else if (null_state && !not_null_state) { /* Definitely NULL. */ return true; } /* Check it’s a valid allocation. */ SymbolRef error_sym = error_location.getAsSymbol (); DEBUG ("Asserting GError* is set: SymbolRef:" << error_sym); if (error_sym == NULL) { return true; } const ErrorState *error_state = _error_map_get (state, error_sym); if (error_state != NULL && error_state->isFreed ()) { ExplodedNode *error_node = context.generateErrorNode (state); this->_initialise_bug_reports (); auto R = llvm::make_unique (*this->_double_free, "Freeing already-freed GError", error_node); R->addRange (source_range); R->addRange (error_state->S); Debug::emit_bug_report (std::move (R), context); return false; } else if (error_state != NULL && !error_state->isSet ()) { ExplodedNode *error_node = context.generateErrorNode (state); this->_initialise_bug_reports (); auto R = llvm::make_unique (*this->_free_cleared, "Freeing non-set GError", error_node); R->addRange (source_range); R->addRange (error_state->S); Debug::emit_bug_report (std::move (R), context); return false; } return true; } /** * Check a GError** is clear before overwriting it. * * Formally, this checks the condition: * ptr_error_location = NULL ∨ (*ptr_error_location) = NULL * * Returns: false on a bug, true otherwise */ bool GErrorChecker::_assert_gerror_ptr_clear (SVal ptr_error_location, ProgramStateRef state, CheckerContext &context, const SourceRange &source_range) const { ProgramStateRef not_null_state, null_state; /* for GError* */ ProgramStateRef ptr_not_null_state, ptr_null_state; /* for GError** */ state = context.getState (); if (!ptr_error_location.getAs ()) { return true; } DefinedOrUnknownSVal _ptr_error_location = ptr_error_location.castAs (); /* Branch on whether the GError** is NULL. If it is, we have nothing to * do. */ std::tie (ptr_not_null_state, ptr_null_state) = state->assume (_ptr_error_location); if (ptr_null_state && !ptr_not_null_state) { /* Definitely NULL. */ return true; } /* Check the GError*. */ SVal error_location = this->_error_from_error_ptr (ptr_error_location, context); return this->_assert_gerror_unset (error_location, false, state, context, source_range); } /** * Check a GError* is NULL (clear) or unset before overwriting it. * * Formally, this checks the condition: * undef_allowed = (error_location = NULL ∨ * (error_location ≠ NULL ∧ ¬valid_allocation(error_location))) * ¬undef_allowed = (error_location = NULL) * * Returns: false on a bug, true otherwise */ bool GErrorChecker::_assert_gerror_unset (SVal error_location, bool undef_allowed, ProgramStateRef state, CheckerContext &context, const SourceRange &source_range) const { ProgramStateRef not_null_state, null_state; /* Branch on whether the GError* is NULL. If it isn’t NULL, there’s a * bug. */ if (error_location.getAs () && !undef_allowed) { ExplodedNode *error_node = context.generateErrorNode (state); this->_initialise_bug_reports (); auto R = llvm::make_unique (*this->_use_uninitialised, "Using uninitialized GError", error_node); #if 0 bugreporter::trackNullOrUndefValue (error_node, stmt, *R); #endif R->addRange (source_range); Debug::emit_bug_report (std::move (R), context); return false; } else if (!error_location.getAs ()) { return true; } DefinedOrUnknownSVal _error_location = error_location.castAs (); std::tie (not_null_state, null_state) = state->assume (_error_location); if (null_state && !not_null_state) { /* Definitely NULL. */ return true; } SymbolRef error_sym = error_location.getAsSymbol (); DEBUG ("Asserting GError* is clear: SymbolRef:" << error_sym); if (error_sym == NULL) { return true; } const ErrorState *error_state = _error_map_get (state, error_sym); if (error_state != NULL && error_state->isSet ()) { ExplodedNode *error_node = context.generateErrorNode (state); this->_initialise_bug_reports (); auto R = llvm::make_unique (*this->_overwrite_set, "Overwriting already-set GError", error_node); R->addRange (source_range); R->addRange (error_state->S); Debug::emit_bug_report (std::move (R), context); return false; } else if (error_state != NULL && error_state->isFreed () && !undef_allowed) { ExplodedNode *error_node = context.generateErrorNode (state); this->_initialise_bug_reports (); auto R = llvm::make_unique (*this->_overwrite_freed, "Overwriting already-freed GError", error_node); R->addRange (source_range); R->addRange (error_state->S); Debug::emit_bug_report (std::move (R), context); return false; } return true; } /** * Check the given error code is a member of a specific error domain. * * Formally, this checks the condition: * code ϵ error_codes(domain) * * Returns: false on a bug, true otherwise */ bool GErrorChecker::_assert_code_in_domain (SVal domain, SVal code, ProgramStateRef state, CheckerContext &context, const SourceRange &domain_source_range, const SourceRange &code_source_range) const { /* FIXME: Implement. */ return true; } /* Set a GError* to a non-NULL value. */ ProgramStateRef GErrorChecker::_set_gerror (SVal error_location, DefinedSVal new_error, ProgramStateRef state, CheckerContext &context, const SourceRange &source_range) const { /* Bind the error location to the new error. */ state = state->bindLoc (error_location, new_error, context.getLocationContext ()); assert (state != NULL); /* Constrain the GError* location (lvalue) and rvalue to be non-NULL. */ SValBuilder &sval_builder = context.getSValBuilder (); DefinedOrUnknownSVal error_rvalue_null = sval_builder.evalEQ (state, new_error, sval_builder.makeNull ()); state = state->assume (error_rvalue_null, false); assert (state != NULL); /* Set the error. */ SymbolRef error_sym = error_location.getAsSymbol (); DEBUG ("Setting GError* mapping: SymbolRef: " << error_sym); if (error_sym == NULL) { return state; } return _error_map_set (state, error_sym, ErrorState::getSet (source_range)); } /* Set a GError* to a NULL value. Note: This does _not_ mark the MemRegion * storing the actual GError as freed. Use _gerror_free() for that. */ ProgramStateRef GErrorChecker::_clear_gerror (SVal error_location, ProgramStateRef state, CheckerContext &context, const SourceRange &source_range) const { /* Bind the GError* to NULL. */ SValBuilder &sval_builder = context.getSValBuilder (); state = state->bindLoc (error_location, sval_builder.makeNull (), context.getLocationContext ()); assert (state != NULL); /* Constrain the GError* location (lvalue) to be NULL. */ if (error_location.getAs ()) { DefinedOrUnknownSVal error_null = sval_builder.evalEQ (state, error_location.castAs (), sval_builder.makeNull ()); ProgramStateRef new_state = state->assume (error_null, true); if (new_state != NULL) { state = new_state; } } else { DEBUG ("Couldn’t get DefinedOrUnknownSVal for error."); } /* Clear the error. */ DEBUG ("Clearing GError* mapping."); SymbolRef error_sym = error_location.getAsSymbol (); if (error_sym == NULL) { return state; } return _error_map_set (state, error_sym, ErrorState::getClear (source_range)); } /* Initialisation may fail if glib.h has not been included. * Return true iff initialisation succeeded. */ bool GErrorChecker::_initialise_identifiers (const ASTContext &context) const { if (!this->_gerror_type.isNull ()) { return true; } TypeManager manager = TypeManager (context); /* Types. */ this->_gerror_type = manager.find_type_by_name ("GError"); /* Functions. */ this->_identifier_g_set_error = &context.Idents.get ("g_set_error"); this->_identifier_g_set_error_literal = &context.Idents.get ("g_set_error_literal"); this->_identifier_g_error_new = &context.Idents.get ("g_error_new"); this->_identifier_g_error_new_literal = &context.Idents.get ("g_error_new_literal"); this->_identifier_g_error_new_valist = &context.Idents.get ("g_error_new_valist"); this->_identifier_g_error_free = &context.Idents.get ("g_error_free"); this->_identifier_g_clear_error = &context.Idents.get ("g_clear_error"); this->_identifier_g_propagate_error = &context.Idents.get ("g_propagate_error"); this->_identifier_g_propagate_prefixed_error = &context.Idents.get ("g_propagate_prefixed_error"); return (!this->_gerror_type.isNull ()); } void GErrorChecker::_initialise_bug_reports () const { if (this->_overwrite_set) { return; } this->_overwrite_set.reset ( new BuiltinBug (this->filter.check_name_overwrite_set, Debug::Categories::GError, "Try to assign over the top of an existing " "GError. Causes loss of error information and " "a memory leak.")); this->_overwrite_freed.reset ( new BuiltinBug (this->filter.check_name_overwrite_freed, Debug::Categories::GError, "Try to assign over the top of an existing " "GError which has been freed but not cleared " "to NULL. g_set_error(!NULL) is not allowed.")); this->_double_free.reset ( new BuiltinBug (this->filter.check_name_double_free, Debug::Categories::GError, "Try to free a GError which has already been " "freed. Causes heap corruption.")); this->_free_cleared.reset ( new BuiltinBug (this->filter.check_name_free_cleared, Debug::Categories::GError, "Try to free a GError which has been cleared to" "NULL. g_error_free(NULL) is not allowed.")); this->_use_uninitialised.reset ( new BuiltinBug (this->filter.check_name_use_uninitialised, Debug::Categories::GError, "Try to use a GError which has not been " "initialized to NULL. Causes spurious " "error reports.")); this->_memory_leak.reset ( new BuiltinBug (this->filter.check_name_memory_leak, Debug::Categories::GError, "Fail to free a GError before it goes out of " "scope.")); } } /* namespace tartan */