Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Dockerfile
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to create a new file Dockerfile-firefox with those changes and leave the original unchanged

Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,45 @@ RUN apt-get update && apt-get install -y \
xz-utils \
tar \
cpio \
pkg-config \
libgtk-3-dev libglib2.0-dev libpango1.0-dev libharfbuzz-dev \
libfreetype6-dev libfontconfig1-dev libgdk-pixbuf-2.0-dev \
libicu-dev libpng-dev libjpeg-turbo8-dev libtiff-dev \
autoconf2.13 nasm yasm zip \
python3-venv \
libx11-dev libx11-xcb-dev libxcb1-dev libxcb-shm0-dev \
libxext-dev libxrandr-dev libxcomposite-dev libxcursor-dev \
libxdamage-dev libxfixes-dev libxi-dev libxtst-dev \
mesa-common-dev libegl1-mesa-dev libopengl-dev \
libasound2-dev libpulse-dev \
libdbus-1-dev libdbus-glib-1-dev \
zlib1g-dev libffi-dev \

&& rm -rf /var/lib/apt/lists/*

# Install Oh My Zsh for prettier shell
RUN sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended

# Install Nodejs 20
RUN set -eux; \
mkdir -p /etc/apt/keyrings; \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" \
> /etc/apt/sources.list.d/nodesource.list; \
apt-get update; \
apt-get install -y --no-install-recommends nodejs; \
node -v && npm -v; \
rm -rf /var/lib/apt/lists/*

# Rust set up
RUN set -eux; \
curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.82.0; \
rustc --version && cargo --version; \
apt-get -y purge cbindgen || true; \
cargo install cbindgen --version 0.26.0 --force; \
cbindgen --version

# Set zsh as default shell
RUN chsh -s $(which zsh)

Expand Down
2 changes: 2 additions & 0 deletions commits/commits-firefox-debug.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
4cf24787d4df11a84fa3ba7eadbb77e9fb915e53,Use-After-Free
76b5668417e02fc6aa260245ad35e8dd1186a063,Call-On-Null-Pointer
10 changes: 10 additions & 0 deletions commits/commits-firefox.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
1b003f8ad9abb9a809098ef5c1f7c215d237c9f4,Null-Pointer-Dereference
24021be559865d82a56fa16c5efbca5065d01b5a,Null-Pointer-Dereference
d2a896eed4209bf34265a5f8921151bc1de87990,Null-Pointer-Dereference
f19fb265799386caafd9dc02f5efa161149b8de8,Null-Pointer-Dereference
4db507facff9908f99cf7ece1108a5f528a8e4c8,Null-Pointer-Dereference
0bf62ba30a2f93ae7380e528a9200fab7c5e4735,Use-After-Free
568f530b13981fa3f72442723623f5d7d34d9f58,Use-After-Free
056813bb47d5c35237ad82dce5efa7705cea98eb,Use-After-Free
c935848bc0d62704ecd407c16e1a0231b67b57b8,Use-After-Free
d025b9ed06bc36beb8719d1e983c4d217ab06ce2,Use-After-Free
216 changes: 216 additions & 0 deletions prompt_template/firefox/examples/double-free/checker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Checkers/Taint.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/Environment.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/StmtVisitor.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/StaticAnalyzer/Checkers/utility.h"


using namespace clang;
using namespace ento;
using namespace taint;

// Define a unique taint tag for devm_ allocations.
static TaintTagType TaintTag = 101;

namespace {

class SAGenTestChecker
: public Checker<eval::Call, // For modeling certain functions
check::PreCall, // For checking pre-call conditions
check::PostCall> // For checking post-call conditions
{
mutable std::unique_ptr<BugType> BT;

public:
// Constructor to initialize the BugType describing our double-free bug.
SAGenTestChecker()
: BT(new BugType(this, "Double Free of devm Allocated Memory",
"Memory Management")) {}

// This callback can be used to model the behavior of functions, including
// allocating memory or mutating states in a custom way.
bool evalCall(const CallEvent &Call, CheckerContext &C) const;

// Post-call check: track when devm_* allocation functions return memory,
// marking the returned pointer as "tainted" (i.e., devm-allocated).
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;

// Pre-call check: if the function is a known free function (kfree, kvfree, or
// pinctrl_utils_free_map), verify if the passed pointer was previously
// devm-allocated. If so, report a double-free issue.
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;

private:
void reportDoubleFree(const CallEvent &Call, CheckerContext &C,
const MemRegion *Region) const;
};

} // end anonymous namespace

/// evalCall - Used to model certain function calls manually. Here, we intercept
/// certain devm_* allocations to simulate a symbolic region allocation.
bool SAGenTestChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
ProgramStateRef State = C.getState();
const IdentifierInfo *Callee = Call.getCalleeIdentifier();
if (!Callee)
return false;

// If the function name matches any of the devm_* memory allocation functions,
// create a symbolic region to represent the newly allocated memory.
if (Callee->getName() == "devm_kcalloc" ||
Callee->getName() == "devm_kmalloc" ||
Callee->getName() == "devm_kzalloc" ||
Callee->getName() == "devm_kmalloc_array") {

// Retrieve the original call expression.
const Expr *expr = Call.getOriginExpr();
if (!expr)
return false;

const CallExpr *CE = dyn_cast<CallExpr>(expr);
if (!CE)
return false;

// Create a conjured symbol representing the allocated memory. This
// effectively simulates an allocation site for the static analyzer.
unsigned Count = C.blockCount();
SValBuilder &svalBuilder = C.getSValBuilder();
const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
DefinedSVal RetVal =
svalBuilder.getConjuredHeapSymbolVal(CE, LCtx, Count).castAs<DefinedSVal>();

// Initialize the symbolic memory with an undefined value. This is optional
// but often done in the analyzer to track data flows.
State = State->bindDefaultInitial(RetVal, UndefinedVal(), LCtx);

// Bind the symbolic allocation to the call expression's return value.
State = State->BindExpr(CE, C.getLocationContext(), RetVal);

// If the return value is not a location, do not continue.
if (!RetVal.getAs<Loc>())
return false;

// Finally, add the new state transition to the analyzer.
if (State)
C.addTransition(State);
}

// This indicates whether the call produced a new or different state.
bool isDifferent = C.isDifferent();
return isDifferent;
}

/// checkPostCall - After the call is evaluated, we mark the returned pointer
/// as tainted if it comes from a devm_* allocation function.
void SAGenTestChecker::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const IdentifierInfo *Callee = Call.getCalleeIdentifier();
if (!Callee)
return;

// If it's one of our target devm_* allocation functions, taint the result.
if (Callee->getName() == "devm_kcalloc" ||
Callee->getName() == "devm_kmalloc" ||
Callee->getName() == "devm_kzalloc" ||
Callee->getName() == "devm_kmalloc_array") {

// Ensure we have a valid call expression.
const CallExpr *CE = dyn_cast<CallExpr>(Call.getOriginExpr());
if (!CE)
return;

// Retrieve the return value.
SVal RetVal = Call.getReturnValue();
SymbolRef retSymbol = RetVal.getAsSymbol();
if (retSymbol) {
// Mark the symbol as "tainted" with our custom TaintTag,
// indicating devm allocation.
State = addTaint(State, retSymbol, TaintTag);
}
// Save the new state.
C.addTransition(State);
}
}

/// checkPreCall - Before kfree, kvfree, or pinctrl_utils_free_map is called,
/// check if the pointer to be freed is tagged as devm-allocated. If so,
/// issue a double-free warning.
void SAGenTestChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const IdentifierInfo *Callee = Call.getCalleeIdentifier();
if (!Callee)
return;

// Handle pinctrl_utils_free_map. Note that the pointer is passed as
// the second argument (index 1).
if (Callee->getName() == "pinctrl_utils_free_map") {
SVal arg1 = Call.getArgSVal(1);
SymbolRef argSymbol = arg1.getAsSymbol();

if (argSymbol) {
// If this symbol was tainted as devm-allocated, report a double-free.
if (isTainted(State, argSymbol, TaintTag)) {
reportDoubleFree(Call, C, arg1.getAsRegion());
}
}
}

// Handle kfree/kvfree. The pointer is the first argument (index 0).
if (Callee->getName() == "kfree" || Callee->getName() == "kvfree") {
SVal arg0 = Call.getArgSVal(0);
SymbolRef argSymbol = arg0.getAsSymbol();

if (argSymbol) {
// If this symbol was tainted as devm-allocated, report a double-free.
if (isTainted(State, argSymbol, TaintTag)) {
reportDoubleFree(Call, C, arg0.getAsRegion());
}
}
}
}

/// reportDoubleFree - Emit a warning if devm-allocated memory is freed using
/// a standard free function, indicating a possible double-free.
void SAGenTestChecker::reportDoubleFree(const CallEvent &Call,
CheckerContext &C,
const MemRegion *Region) const {
if (!BT)
return;

// Generate a node in the exploded graph for this error.
ExplodedNode *N = C.generateNonFatalErrorNode();
if (!N)
return;

// Create and populate a bug report object.
auto report = std::make_unique<PathSensitiveBugReport>(
*BT, "Double free of devm_* allocated memory", N);
report->addRange(Call.getSourceRange());
C.emitReport(std::move(report));
}

//===----------------------------------------------------------------------===//
// Checker Registration
//===----------------------------------------------------------------------===//

extern "C" void clang_registerCheckers(CheckerRegistry &registry) {
registry.addChecker<SAGenTestChecker>(
"custom.SAGenTestChecker",
"Detects double free of memory allocated by devm_* functions",
"");
}

extern "C" const char clang_analyzerAPIVersionString[] =
CLANG_ANALYZER_API_VERSION_STRING;
Loading