diff --git a/svf-llvm/tools/AE/ae.cpp b/svf-llvm/tools/AE/ae.cpp index 2d5889430..652a57128 100644 --- a/svf-llvm/tools/AE/ae.cpp +++ b/svf-llvm/tools/AE/ae.cpp @@ -885,6 +885,8 @@ int main(int argc, char** argv) AbstractInterpretation& ae = AbstractInterpretation::getAEInstance(); if (Options::BufferOverflowCheck()) ae.addDetector(std::make_unique()); + if (Options::NullPtrDereference()) + ae.addDetector(std::make_unique()); ae.runOnModule(pag->getICFG()); LLVMModuleSet::releaseLLVMModuleSet(); diff --git a/svf/include/AE/Core/AddressValue.h b/svf/include/AE/Core/AddressValue.h index 3fb4ab49c..bda9ad76f 100644 --- a/svf/include/AE/Core/AddressValue.h +++ b/svf/include/AE/Core/AddressValue.h @@ -32,7 +32,7 @@ #define AddressMask 0x7f000000 #define FlippedAddressMask (AddressMask^0xffffffff) -// the address of the black hole, getVirtualMemAddress(2); +// the address of the black hole, getVirtualMemAddress(2); Also used to represent nullptr. #define BlackHoleAddr 0x7f000000 + 2 #include "Util/GeneralType.h" diff --git a/svf/include/AE/Svfexe/AEDetector.h b/svf/include/AE/Svfexe/AEDetector.h index 307b589ed..fe6d580bf 100644 --- a/svf/include/AE/Svfexe/AEDetector.h +++ b/svf/include/AE/Svfexe/AEDetector.h @@ -45,7 +45,8 @@ class AEDetector enum DetectorKind { BUF_OVERFLOW, ///< Detector for buffer overflow issues. - UNKNOWN, ///< Default type if the kind is not specified. + NULLPTR_DEREF, ///< Detector for null pointer dereference issues. + UNKNOWN, ///< Default type if the kind is not specified. }; /** @@ -320,4 +321,118 @@ class BufOverflowDetector : public AEDetector SVFBugReport recoder; ///< Recorder for abstract execution bugs. Map nodeToBugInfo; ///< Maps ICFG nodes to bug information. }; + + +/** + * @class NullPtrDerefDetector + * @brief Detector for identifying null pointer dereference issues. + */ +class NullPtrDerefDetector: public AEDetector { + friend class AbstractInterpretation; +public: + /** + * @brief Constructor initializes the detector kind to NULLPTR_DEREF. + */ + NullPtrDerefDetector() + { + kind = NULLPTR_DEREF; + } + + /** + * @brief Destructor. + */ + ~NullPtrDerefDetector() = default; + + /** + * @brief Check if the detector is of the NULLPTR_DEREF kind. + * @param detector Pointer to the detector. + * @return True if the detector is of type NULLPTR_DEREF, false otherwise. + */ + static bool classof(const AEDetector* detector) + { + return detector->getKind() == AEDetector::NULLPTR_DEREF; + } + + + /** + * @brief Detect null pointer dereferece issues within a node. + * @param as Reference to the abstract state. + * @param node Pointer to the ICFG node. + */ + void detect(AbstractState& as, const ICFGNode* node); + + /** + * @brief Handles external API calls related to null pointer dereference detection. + * @param call Pointer to the call ICFG node. + */ + void handleStubFunctions(const CallICFGNode*); + + /** + * @brief Check if an Abstract Value is NULL (or uninitialized). + * + * @param v An Abstract Value of loaded from an address in an Abstract State. + */ + bool isNull(AbstractValue v) { + bool is = (v.getInterval().isBottom() && v.getAddrs().isBottom()) || + v.getAddrs().contains(BlackHoleAddr); + return is; + } + + /** + * @brief Check if an Abstract Value is uninitialized. + * + * @param v An Abstract Value in an Abstract State. + */ + bool isUninit(AbstractValue v) { + bool is = (v.getInterval().isBottom() && v.getAddrs().isBottom()); + return is; + } + + /** + * @brief Reports all detected null pointer dereference bugs. + */ + void reportBug() + { + std::cerr << "###################### Null Pointer Dereference (" + std::to_string(stmtToBugInfo.size()) + + " found) ######################\n"; + std::cerr << "---------------------------------------------\n"; + for (const auto& it : stmtToBugInfo) { + const SVFStmt *s = it.first; + std::cerr << "Location: " << s->toString() << "\n---------------------------------------------\n"; + } + } + + /** + * @brief record a bug. + * @param stmt The SVF statement that triggers the null pointer dereference. + */ + void recordBug(const SVFStmt *stmt) + { + const ICFGNode *eventInst = stmt->getICFGNode(); + SVFBugEvent sourceInstEvent(SVFBugEvent::EventType::SourceInst, eventInst); + + std::string loc = sourceInstEvent.getEventLoc(); // Get the location of the last event in the stack + + if (loc == "") { + stmtToBugInfo[stmt] = loc; + } else if (bugLoc.find(loc) == bugLoc.end()) { + bugLoc.insert(loc); + stmtToBugInfo[stmt] = loc; + } + } + + /** + * @brief Checks if pointer can be safely dereferenced. + * @param as Reference to the abstract state. + * @param value Pointer to the SVF value. + * @return True if the pointer dereference is safe, false otherwise. + */ + bool canSafelyDerefPtr(AbstractState& as, const SVF::SVFVar* value); + + +private: + Set bugLoc; ///< Set of locations where bugs have been reported. + Map stmtToBugInfo; ///< Maps SVF stmt to bug information. +}; + } diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index e33cb4507..d10b24e95 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -104,7 +104,7 @@ class AbstractInterpretation friend class AEStat; friend class AEAPI; friend class BufOverflowDetector; - + friend class NullPtrDerefDetector; public: typedef SCCDetection CallGraphSCC; /// Constructor diff --git a/svf/include/Util/Options.h b/svf/include/Util/Options.h index 54cb7ac68..66d8a78f6 100644 --- a/svf/include/Util/Options.h +++ b/svf/include/Util/Options.h @@ -250,6 +250,8 @@ class Options static const Option OutputName; /// buffer overflow checker, Default: false static const Option BufferOverflowCheck; + /// null pointer dereference checker, Default: false + static const Option NullPtrDereference; /// memory leak check, Default: false static const Option MemoryLeakCheck; /// file open close checker, Default: false diff --git a/svf/lib/AE/Svfexe/AEDetector.cpp b/svf/lib/AE/Svfexe/AEDetector.cpp index aceb19a6e..4136a0a97 100644 --- a/svf/lib/AE/Svfexe/AEDetector.cpp +++ b/svf/lib/AE/Svfexe/AEDetector.cpp @@ -502,3 +502,97 @@ bool BufOverflowDetector::canSafelyAccessMemory(AbstractState& as, const SVF::SV } return true; } + +/** + * @brief Detects null pointer dereference issues within a given ICFG node. + * + * @param as Reference to the abstract state. + * @param node Pointer to the ICFG node. + */ +void NullPtrDerefDetector::detect(AbstractState& as, const ICFGNode* node) { + for (const SVFStmt* stmt : node->getSVFStmts()) { + if (const LoadStmt* load = SVFUtil::dyn_cast(stmt)) { + if (!canSafelyDerefPtr(as, load->getRHSVar())) { + recordBug(load); + } + } else if (const StoreStmt* store = SVFUtil::dyn_cast(stmt)) { + if (!canSafelyDerefPtr(as, store->getLHSVar())) { + recordBug(store); + } + } + } +} + +/** + * @brief Checks if pointer can be safely dereferenced. + * @param as Reference to the abstract state. + * @param value Pointer to the SVF value of the pointer being dereferenced. + * @return True if the pointer dereference is safe, false otherwise. + */ +bool NullPtrDerefDetector::canSafelyDerefPtr(AbstractState& as, const SVF::SVFVar* value) { + NodeID value_id = value->getId(); + if (isUninit(as[value_id])) return false; + if (!as[value_id].isAddr()) return true; // Loading an Interval Value + AbstractValue &addrs = as[value_id]; + for (const auto &addr: addrs.getAddrs()) { + if (isNull(as.load(addr))) + return false; + } + return true; +} + + +/** + * @brief Handles stub functions within the ICFG node. + * + * @param callNode Pointer to the ICFG node. + */ +void NullPtrDerefDetector::handleStubFunctions(const SVF::CallICFGNode* callNode) +{ + // get function name + std::string funcName = callNode->getCalledFunction()->getName(); + if (funcName == "UNSAFE_LOAD") + { + // void UNSAFE_LOAD(void* ptr); + AbstractInterpretation::getAEInstance().checkpoints.erase(callNode); + if (callNode->arg_size() < 1) + return; + AbstractState& as = + AbstractInterpretation::getAEInstance().getAbsStateFromTrace( + callNode); + + const SVFVar* arg0Val = callNode->getArgument(0); + bool isSafe = canSafelyDerefPtr(as, arg0Val); + if (!isSafe) { + std::cout << "detect null pointer deference success: " << callNode->toString() << std::endl; + return; + } + else + { + std::string err_msg = "this UNSAFE_LOAD should be a null pointer dereference but not detected. Pos: "; + err_msg += callNode->getSourceLoc(); + std::cerr << err_msg << std::endl; + assert(false); + } + } + else if (funcName == "SAFE_LOAD") + { + // void SAFE_LOAD(void* ptr); + AbstractInterpretation::getAEInstance().checkpoints.erase(callNode); + if (callNode->arg_size() < 1) return; + AbstractState&as = AbstractInterpretation::getAEInstance().getAbsStateFromTrace(callNode); + const SVFVar* arg0Val = callNode->getArgument(0); + bool isSafe = canSafelyDerefPtr(as, arg0Val); + if (isSafe) { + std::cout << "safe load pointer success: " << callNode->toString() << std::endl; + return; + } + else + { + std::string err_msg = "this SAFE_LOAD should be a safe but a null pointer dereference detected. Pos: "; + err_msg += callNode->getSourceLoc(); + std::cerr << err_msg << std::endl; + assert(false); + } + } +} diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 8ea21a40a..828202afb 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -123,7 +123,7 @@ void AbstractInterpretation::handleGlobalNode() { const ICFGNode* node = icfg->getGlobalICFGNode(); abstractTrace[node] = AbstractState(); - abstractTrace[node][SymbolTableInfo::NullPtr] = AddressValue(); + abstractTrace[node][SymbolTableInfo::NullPtr] = AddressValue(BlackHoleAddr); // Use BlackHoleAddr to represent nullptr // Global Node, we just need to handle addr, load, store, copy and gep for (const SVFStmt *stmt: node->getSVFStmts()) { @@ -917,6 +917,7 @@ void AbstractInterpretation::collectCheckPoint() // traverse every ICFGNode Set ae_checkpoint_names = {"svf_assert"}; Set buf_checkpoint_names = {"UNSAFE_BUFACCESS", "SAFE_BUFACCESS"}; + Set nullptr_checkpoint_names = {"UNSAFE_LOAD", "SAFE_LOAD"}; for (auto it = svfir->getICFG()->begin(); it != svfir->getICFG()->end(); ++it) { @@ -938,6 +939,14 @@ void AbstractInterpretation::collectCheckPoint() checkpoints.insert(call); } } + if (Options::NullPtrDereference()) + { + if (nullptr_checkpoint_names.find(fun->getName()) != + nullptr_checkpoint_names.end()) + { + checkpoints.insert(call); + } + } } } } diff --git a/svf/lib/Util/Options.cpp b/svf/lib/Util/Options.cpp index 838d159d4..a27fe21ae 100644 --- a/svf/lib/Util/Options.cpp +++ b/svf/lib/Util/Options.cpp @@ -785,6 +785,8 @@ const Option Options::OutputName( "output","output db file","output.db"); const Option Options::BufferOverflowCheck( "overflow","Buffer Overflow Detection",false); +const Option Options::NullPtrDereference( + "nullptr","Null Pointer Dereference Detection",false); const Option Options::MemoryLeakCheck( "leak", "Memory Leak Detection",false); const Option Options::FileCheck(