mirror of
https://github.com/google/pebble.git
synced 2025-09-02 10:25:45 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
287
checkers/MutexChecker.cpp
Normal file
287
checkers/MutexChecker.cpp
Normal file
|
@ -0,0 +1,287 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "clang/StaticAnalyzer/Core/Checker.h"
|
||||
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
||||
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
|
||||
#include "clang/StaticAnalyzer/Core/CheckerRegistry.h"
|
||||
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace ento;
|
||||
|
||||
namespace std {
|
||||
void terminate( void ) _NOEXCEPT {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
/* This analyzer suffers from the major limitation that most of the mutexes in Pebble are globals,
|
||||
* so all symbols and MemRegions refering to the mutexes are invalidated every time an unknown
|
||||
* function is called. This analyzer instead associates mutexes with the declaration of their
|
||||
* variables, which has the obvious limitation of not catching when mutexes are passed as
|
||||
* arguments (which fortunately never? happens in pebble).
|
||||
*/
|
||||
|
||||
class MutexState {
|
||||
private:
|
||||
bool locked;
|
||||
bool recursive;
|
||||
unsigned lockCount;
|
||||
public:
|
||||
MutexState(bool isLocked, bool isRecursive, unsigned startCount)
|
||||
:
|
||||
locked(isLocked),
|
||||
recursive(isRecursive),
|
||||
lockCount(startCount)
|
||||
{}
|
||||
|
||||
MutexState getLocked() const {
|
||||
if (recursive) {
|
||||
if (locked) {
|
||||
// Preserve the first lock function (it should be the last one to unlock)
|
||||
return MutexState(true, true, lockCount + 1);
|
||||
}
|
||||
else {
|
||||
return MutexState(true, true, lockCount + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return MutexState(true, false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
MutexState getUnlocked(void) const {
|
||||
if (recursive) {
|
||||
// If lockCount is one, we unlock
|
||||
return MutexState(lockCount > 1, true, lockCount - 1);
|
||||
}
|
||||
else {
|
||||
return MutexState(false, false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool isLocked(void) const {
|
||||
return locked;
|
||||
}
|
||||
|
||||
bool isRecursive(void) const {
|
||||
return recursive;
|
||||
}
|
||||
|
||||
bool operator==(const MutexState &other) const {
|
||||
return locked == other.locked && recursive == other.recursive &&
|
||||
lockCount == other.lockCount;
|
||||
}
|
||||
|
||||
void Profile(llvm::FoldingSetNodeID &ID) const {
|
||||
ID.AddBoolean(locked);
|
||||
ID.AddBoolean(recursive);
|
||||
ID.AddInteger(lockCount);
|
||||
}
|
||||
};
|
||||
|
||||
// Map mutex declarations to state info
|
||||
REGISTER_MAP_WITH_PROGRAMSTATE(MutexMap, const Decl *, MutexState);
|
||||
|
||||
// Hold an ordered list of the mutexes to catch lock order reversal
|
||||
REGISTER_LIST_WITH_PROGRAMSTATE(MutexList, const Decl *);
|
||||
|
||||
namespace {
|
||||
class MutexChecker : public Checker<check::PostCall, check::EndFunction> {
|
||||
std::unique_ptr<BugType> NoUnlockBugType;
|
||||
std::unique_ptr<BugType> DoubleLockBugType;
|
||||
std::unique_ptr<BugType> DoubleUnlockBugType;
|
||||
std::unique_ptr<BugType> TooManyUnlocksBugType;
|
||||
std::unique_ptr<BugType> UnlockNoLockBugType;
|
||||
std::unique_ptr<BugType> LockReversalBugType;
|
||||
|
||||
void reportError(const std::unique_ptr<BugType> &bugType, StringRef msg, CheckerContext &C) const {
|
||||
ExplodedNode *endNode = C.generateSink();
|
||||
if (!endNode) {
|
||||
return;
|
||||
}
|
||||
BugReport *bug = new BugReport(*bugType, msg, endNode);
|
||||
C.emitReport(bug);
|
||||
}
|
||||
|
||||
ProgramStateRef lockMutex(const Decl *mutexDecl, const MutexState *curMutex,
|
||||
ProgramStateRef state, bool recursive = false) const {
|
||||
|
||||
state = state->add<MutexList>(mutexDecl);
|
||||
if (curMutex) {
|
||||
MutexState lockedMutex = curMutex->getLocked();
|
||||
return state->set<MutexMap>(mutexDecl, lockedMutex);
|
||||
}
|
||||
else {
|
||||
MutexState newMutex(true, recursive, recursive ? 1 : 0);
|
||||
return state->set<MutexMap>(mutexDecl, newMutex);
|
||||
}
|
||||
}
|
||||
|
||||
const Decl * getMutexDecl(const Expr *argExpr) const {
|
||||
const Expr *strippedExpr = argExpr->IgnoreParenCasts();
|
||||
const DeclRefExpr *ref = dyn_cast<DeclRefExpr>(strippedExpr);
|
||||
if (ref) {
|
||||
return ref->getDecl();
|
||||
}
|
||||
// If it wasn't a DeclRef maybe it was a member?
|
||||
const MemberExpr *member = dyn_cast<MemberExpr>(strippedExpr);
|
||||
if (member) {
|
||||
return member->getMemberDecl();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void handleLock(StringRef funcName, const CallEvent &call, CheckerContext &C) const {
|
||||
const Decl *mutexDecl = getMutexDecl(call.getArgExpr(0));
|
||||
|
||||
if (!mutexDecl) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramStateRef state = C.getState();
|
||||
|
||||
const MutexState *curMutex = state->get<MutexMap>(mutexDecl);
|
||||
|
||||
if (funcName.equals("mutex_lock") || funcName.equals("mutex_lock_with_lr")) {
|
||||
if (curMutex) {
|
||||
if (curMutex->isLocked()) {
|
||||
reportError(DoubleLockBugType, "This lock was already locked", C);
|
||||
return;
|
||||
}
|
||||
}
|
||||
state = lockMutex(mutexDecl, curMutex, state);
|
||||
C.addTransition(state);
|
||||
}
|
||||
else if (funcName.equals("mutex_lock_with_timeout")) {
|
||||
if (curMutex) {
|
||||
if (curMutex->isLocked()) {
|
||||
reportError(DoubleLockBugType, "This lock was already locked", C);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// diverge into two states, one where we get the mutex and one
|
||||
// where we don't
|
||||
ProgramStateRef lockedState, timeoutState;
|
||||
|
||||
DefinedSVal retVal = call.getReturnValue().castAs<DefinedSVal>();
|
||||
std::tie(lockedState, timeoutState) = state->assume(retVal);
|
||||
|
||||
lockedState = lockMutex(mutexDecl, curMutex, lockedState);
|
||||
|
||||
C.addTransition(lockedState);
|
||||
C.addTransition(timeoutState);
|
||||
}
|
||||
else if (funcName.equals("mutex_lock_recursive")) {
|
||||
state = lockMutex(mutexDecl, curMutex, state, true);
|
||||
C.addTransition(state);
|
||||
}
|
||||
else if (funcName.equals("mutex_lock_recursive_with_timeout") ||
|
||||
funcName.equals("mutex_lock_recursive_with_timeout_and_lr")) {
|
||||
ProgramStateRef lockedState, timeoutState;
|
||||
|
||||
DefinedSVal retVal = call.getReturnValue().castAs<DefinedSVal>();
|
||||
std::tie(lockedState, timeoutState) = state->assume(retVal);
|
||||
|
||||
lockedState = lockMutex(mutexDecl, curMutex, lockedState, true);
|
||||
|
||||
C.addTransition(lockedState);
|
||||
C.addTransition(timeoutState);
|
||||
}
|
||||
}
|
||||
|
||||
void handleUnlock(StringRef funcName, const CallEvent &call, CheckerContext &C) const {
|
||||
if (!(funcName.equals("mutex_unlock") || funcName.equals("mutex_unlock_recursive"))) {
|
||||
return;
|
||||
}
|
||||
ProgramStateRef state = C.getState();
|
||||
|
||||
const Decl *mutexDecl = getMutexDecl(call.getArgExpr(0));
|
||||
const MutexState *curMutex = state->get<MutexMap>(mutexDecl);
|
||||
|
||||
// If it isn't in the map, we never locked it
|
||||
if (!curMutex) {
|
||||
reportError(UnlockNoLockBugType, "Mutex was never locked", C);
|
||||
return;
|
||||
}
|
||||
// If it is in the map but unlocked, it was unlocked twice
|
||||
if (!curMutex->isLocked()) {
|
||||
if (curMutex->isRecursive()) {
|
||||
reportError(TooManyUnlocksBugType, "Recursive mutex already fully unlocked", C);
|
||||
}
|
||||
else {
|
||||
reportError(DoubleUnlockBugType, "Mutex already unlocked", C);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const Decl *lastDecl = state->get<MutexList>().getHead();
|
||||
|
||||
if (mutexDecl != lastDecl) {
|
||||
reportError(LockReversalBugType, "This was not the most recently acquired lock", C);
|
||||
return;
|
||||
}
|
||||
|
||||
state = state->set<MutexList>(state->get<MutexList>().getTail());
|
||||
state = state->set<MutexMap>(mutexDecl, curMutex->getUnlocked());
|
||||
C.addTransition(state);
|
||||
}
|
||||
|
||||
public:
|
||||
MutexChecker(void)
|
||||
: NoUnlockBugType(new BugType(this, "Failure to call unlock", "Pebble Mutex Plugin")),
|
||||
DoubleLockBugType(new BugType(this, "Double Lock", "Pebble Mutex Plugin")),
|
||||
DoubleUnlockBugType(new BugType(this, "Double Unlock", "Pebble Mutex Plugin")),
|
||||
TooManyUnlocksBugType(new BugType(this, "More unlocks than locks", "Pebble Mutex Plugin")),
|
||||
UnlockNoLockBugType(new BugType(this, "Unlock called before lock", "Pebble Mutex Plugin")),
|
||||
LockReversalBugType(new BugType(this, "Lock order reversal", "Pebble Mutex Plugin"))
|
||||
{}
|
||||
|
||||
void checkPostCall(const CallEvent &call, CheckerContext &C) const {
|
||||
const IdentifierInfo *identInfo = call.getCalleeIdentifier();
|
||||
if(!identInfo) {
|
||||
return;
|
||||
}
|
||||
StringRef funcName = identInfo->getName();
|
||||
if (funcName.startswith("mutex_lock")) {
|
||||
handleLock(funcName, call, C);
|
||||
}
|
||||
else if (funcName.startswith("mutex_unlock")) {
|
||||
handleUnlock(funcName, call, C);
|
||||
}
|
||||
}
|
||||
|
||||
void checkEndFunction(CheckerContext &C) const {
|
||||
ProgramStateRef state = C.getState();
|
||||
|
||||
if (C.inTopFrame()) {
|
||||
// This path ends once this function ends
|
||||
for (auto mutexPair : state->get<MutexMap>()) {
|
||||
if (mutexPair.second.isLocked()) {
|
||||
reportError(NoUnlockBugType, "Mutex still locked at end of path", C);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" const char clang_analyzerAPIVersionString[] = CLANG_ANALYZER_API_VERSION_STRING;
|
||||
|
||||
extern "C" void clang_registerCheckers(CheckerRegistry ®istry) {
|
||||
registry.addChecker<MutexChecker>("pebble.MutexChecker", "Checker for use of mutex_lock()/mutex_unlock()");
|
||||
}
|
267
checkers/SyscallSecurityChecker.cpp
Normal file
267
checkers/SyscallSecurityChecker.cpp
Normal file
|
@ -0,0 +1,267 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "clang/StaticAnalyzer/Core/Checker.h"
|
||||
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
||||
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
|
||||
#include "clang/StaticAnalyzer/Core/CheckerRegistry.h"
|
||||
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace ento;
|
||||
|
||||
namespace std {
|
||||
void terminate( void ) _NOEXCEPT {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
// Need to specialize for any custom types used in traits
|
||||
// Look in include/clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h
|
||||
namespace clang {
|
||||
namespace ento {
|
||||
template <> struct ProgramStatePartialTrait<const FunctionDecl *> {
|
||||
typedef const FunctionDecl * data_type;
|
||||
|
||||
static inline data_type MakeData(void *const* p) {
|
||||
return p ? (const FunctionDecl *)*p : data_type();
|
||||
}
|
||||
|
||||
static inline void *MakeVoidPtr(data_type d) {
|
||||
return const_cast<FunctionDecl *>(d);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* Ultimately this would work better / be more thorough if it made use of the Analyzer's
|
||||
* taint checking, but there is no infrastructure to remove taint at the moment.
|
||||
*/
|
||||
|
||||
REGISTER_TRAIT_WITH_PROGRAMSTATE(CurrentSyscallState, const FunctionDecl *);
|
||||
// Args are tracked by their MemRegion
|
||||
REGISTER_SET_WITH_PROGRAMSTATE(TaintedArgsState, const MemRegion *);
|
||||
|
||||
namespace {
|
||||
class SyscallSecurityChecker :
|
||||
public Checker< eval::Call, check::PreCall, check::Location, check::Bind, check::EndFunction > {
|
||||
|
||||
std::unique_ptr<BugType> NoBoundsCheckBugType;
|
||||
std::unique_ptr<BugType> UnsafeCallBugType;
|
||||
|
||||
llvm::StringSet<> unsafeFunctions {};
|
||||
|
||||
const FunctionDecl * getCurrentSyscall(const ProgramStateRef state) const {
|
||||
return state->get<CurrentSyscallState>();
|
||||
}
|
||||
|
||||
bool inSyscall(const ProgramStateRef state) const {
|
||||
return !!getCurrentSyscall(state);
|
||||
}
|
||||
|
||||
ProgramStateRef setCurrentSyscall(const ProgramStateRef state, const FunctionDecl *FD) const {
|
||||
return state->set<CurrentSyscallState>(FD);
|
||||
}
|
||||
|
||||
bool isValTainted(const SVal &arg, const ProgramStateRef state) const {
|
||||
const MemRegion *MR = arg.getAsRegion();
|
||||
if (!MR) {
|
||||
return false;
|
||||
}
|
||||
const MemRegion *baseMR = MR->getBaseRegion();
|
||||
|
||||
return state->contains<TaintedArgsState>(baseMR);
|
||||
}
|
||||
|
||||
void reportUnsanitizedUse(const SVal &arg, const ProgramStateRef state, CheckerContext &C) const {
|
||||
ExplodedNode *errNode = C.generateSink();
|
||||
if (!errNode) {
|
||||
// Already reported an error here
|
||||
return;
|
||||
}
|
||||
BugReport *R = new BugReport(*NoBoundsCheckBugType,
|
||||
"Used an unsanitized argument from syscall", errNode);
|
||||
R->markInteresting(arg);
|
||||
C.emitReport(R);
|
||||
}
|
||||
|
||||
public:
|
||||
SyscallSecurityChecker(void)
|
||||
: NoBoundsCheckBugType(new BugType(this, "Failed to check bounds", "Pebble Syscall Plugin")),
|
||||
UnsafeCallBugType(new BugType(this, "Syscall used dangerous function", "Pebble Syscall Plugin"))
|
||||
{
|
||||
StringRef funcs[] = { "task_malloc", "task_zalloc", "task_calloc", "app_malloc", "app_zalloc", "app_calloc" };
|
||||
|
||||
// It would be more efficient to look up the IdentifierInfos for each of these and compare against that
|
||||
for (StringRef func : funcs) {
|
||||
unsafeFunctions.insert(func);
|
||||
}
|
||||
}
|
||||
|
||||
bool evalCall(const CallExpr *call, CheckerContext &C) const {
|
||||
if (!C.getCalleeName(call).equals("syscall_internal_elevate_privilege")) {
|
||||
return false;
|
||||
}
|
||||
// Always return true from syscall_internal_elevate_privilege
|
||||
// so the analyzer always thinks privileges have been elevated
|
||||
|
||||
ProgramStateRef state = C.getState();
|
||||
|
||||
SVal ret = C.getSValBuilder().makeTruthVal(true);
|
||||
state = state->BindExpr(call, C.getLocationContext(), ret);
|
||||
|
||||
C.addTransition(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
void checkPreCall(const CallEvent &call, CheckerContext &C) const {
|
||||
const IdentifierInfo *identInfo = call.getCalleeIdentifier();
|
||||
if(!identInfo) {
|
||||
return;
|
||||
}
|
||||
StringRef funcName = identInfo->getName();
|
||||
|
||||
ProgramStateRef state = C.getState();
|
||||
|
||||
if (funcName.equals("syscall_internal_elevate_privilege")) {
|
||||
const LocationContext *LCtx = C.getLocationContext();
|
||||
const FunctionDecl *FD = dyn_cast<FunctionDecl>(LCtx->getDecl());
|
||||
if (!FD) {
|
||||
llvm::errs() << "Privileges elevated outside of function?\n";
|
||||
return;
|
||||
}
|
||||
|
||||
ExplodedNode *pred = NULL;
|
||||
|
||||
// If we're not at the top level, we generate two new transitions, one for the current syscall
|
||||
// executing normally, and one which simulates execution starting at this syscall.
|
||||
// This is important, because if a syscall is called by another function, the syscall
|
||||
// will not be treated as an entry point by the analyzer.
|
||||
|
||||
if (!C.inTopFrame()) {
|
||||
C.addTransition(state);
|
||||
state = C.getStateManager().getInitialState(LCtx);
|
||||
// Get the first node in the state graph
|
||||
pred = C.getPredecessor();
|
||||
while (pred->getFirstPred()) {
|
||||
pred = pred->getFirstPred();
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < FD->getNumParams(); i++) {
|
||||
// We only care about tracking pointer arguments
|
||||
const ParmVarDecl *ParamDecl = FD->getParamDecl(i);
|
||||
if (ParamDecl->getType()->isPointerType()) {
|
||||
// Find the MemRegion associated with the parameter.
|
||||
// Seems very roundabout, but it works...
|
||||
// Remember to look at state->getRegion
|
||||
Loc lValue = state->getLValue(ParamDecl, LCtx);
|
||||
SVal valRegion = state->getSVal(lValue);
|
||||
if (valRegion == UnknownVal()) {
|
||||
llvm::errs() << "Failed to get argument SymbolRef\n";
|
||||
continue;
|
||||
}
|
||||
const MemRegion *MR = valRegion.getAsRegion();
|
||||
if (!MR) {
|
||||
llvm::errs() << "No region for ptr argument\n";
|
||||
continue;
|
||||
}
|
||||
state = state->add<TaintedArgsState>(MR);
|
||||
}
|
||||
}
|
||||
state = setCurrentSyscall(state, FD);
|
||||
C.addTransition(state, pred, nullptr);
|
||||
}
|
||||
else if (inSyscall(state)) {
|
||||
if (funcName.equals("syscall_assert_userspace_buffer")) {
|
||||
const MemRegion *MR = call.getArgSVal(0).getAsRegion();
|
||||
|
||||
state = state->remove<TaintedArgsState>(MR);
|
||||
}
|
||||
else if (funcName.equals("memory_layout_is_cstring_in_region") ||
|
||||
funcName.equals("memory_layout_is_pointer_in_region")) {
|
||||
const MemRegion *MR = call.getArgSVal(1).getAsRegion();
|
||||
|
||||
state = state->remove<TaintedArgsState>(MR);
|
||||
}
|
||||
// Make sure the syscall isn't calling an unsafe function
|
||||
else if (unsafeFunctions.count(funcName)) {
|
||||
ExplodedNode *errNode = C.generateSink();
|
||||
if (!errNode) {
|
||||
// Already reported an error here
|
||||
return;
|
||||
}
|
||||
BugReport *R = new BugReport(*UnsafeCallBugType,
|
||||
"This function shouldn't be called from privileged code", errNode);
|
||||
C.emitReport(R);
|
||||
return;
|
||||
}
|
||||
else { // Any other function, just want to make sure it isn't getting the unsanitized args
|
||||
for (unsigned i = 0; i < call.getNumArgs(); i++) {
|
||||
SVal argVal = call.getArgSVal(i);
|
||||
if (isValTainted(argVal, state)) {
|
||||
reportUnsanitizedUse(argVal, state, C);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
C.addTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
void checkLocation(SVal loc, bool isLoad, const Stmt *S, CheckerContext &C) const {
|
||||
ProgramStateRef state = C.getState();
|
||||
|
||||
if (isValTainted(loc, state)) {
|
||||
reportUnsanitizedUse(loc, state, C);
|
||||
}
|
||||
}
|
||||
|
||||
void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const {
|
||||
ProgramStateRef state = C.getState();
|
||||
|
||||
if (isValTainted(val, state)) {
|
||||
reportUnsanitizedUse(val, state, C);
|
||||
}
|
||||
}
|
||||
|
||||
void checkEndFunction(CheckerContext &C) const {
|
||||
ProgramStateRef state = C.getState();
|
||||
|
||||
const Decl *D = C.getLocationContext()->getDecl();
|
||||
const FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
|
||||
if (!FD) {
|
||||
// Not sure why this would ever be the case...
|
||||
llvm::errs() << "Path ended outside of function?\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (FD != getCurrentSyscall(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we are effectively emulating every syscall as an entry point from
|
||||
// the analyzer's perspective, once the syscall is done, end the path.
|
||||
C.generateSink();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" const char clang_analyzerAPIVersionString[] = CLANG_ANALYZER_API_VERSION_STRING;
|
||||
|
||||
extern "C" void clang_registerCheckers(CheckerRegistry ®istry) {
|
||||
registry.addChecker<SyscallSecurityChecker>("pebble.SyscallSecurityChecker", "Checker that makes sure pointer arguments to syscalls are sanitized");
|
||||
}
|
153
checkers/test-programs/mutex-test.c
Normal file
153
checkers/test-programs/mutex-test.c
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct Mutex;
|
||||
struct RecursiveMutex;
|
||||
|
||||
typedef struct Mutex * restrict mutex_t;
|
||||
typedef struct RecursiveMutex * recursive_mutex_t;
|
||||
|
||||
extern void mutex_lock(mutex_t);
|
||||
extern void mutex_unlock(mutex_t);
|
||||
|
||||
extern bool mutex_lock_with_timeout(mutex_t);
|
||||
|
||||
extern void mutex_lock_recursive(recursive_mutex_t);
|
||||
extern void mutex_unlock_recursive(recursive_mutex_t);
|
||||
|
||||
static mutex_t global_lock = 0;
|
||||
static mutex_t global_lock2;
|
||||
mutex_t recursive_lock;
|
||||
|
||||
void nounlock() {
|
||||
mutex_lock(global_lock);
|
||||
}
|
||||
|
||||
void nolock() {
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
|
||||
void normal() {
|
||||
mutex_lock(global_lock);
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
|
||||
struct handle {
|
||||
mutex_t m;
|
||||
} m_wrapper;
|
||||
|
||||
extern int do_stuff(struct handle * h);
|
||||
|
||||
void structthing(struct handle * h) {
|
||||
mutex_lock(h->m);
|
||||
do_stuff(h);
|
||||
mutex_unlock(h->m);
|
||||
}
|
||||
|
||||
extern int do_stuff2();
|
||||
|
||||
void stuff() {
|
||||
mutex_lock(global_lock);
|
||||
|
||||
do_stuff2();
|
||||
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
|
||||
void stuff2() {
|
||||
mutex_lock(m_wrapper.m);
|
||||
|
||||
do_stuff2();
|
||||
|
||||
mutex_unlock(m_wrapper.m);
|
||||
}
|
||||
|
||||
void nest2() {
|
||||
mutex_lock(global_lock);
|
||||
printf("blah %p", global_lock);
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
|
||||
void nest() {
|
||||
nest2();
|
||||
}
|
||||
|
||||
void cond(void *glob_ptr) {
|
||||
mutex_lock(global_lock);
|
||||
|
||||
while (glob_ptr) {
|
||||
printf("blah %p", glob_ptr);
|
||||
}
|
||||
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
|
||||
void timeout() {
|
||||
mutex_lock_with_timeout(global_lock);
|
||||
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
|
||||
void good_timeout() {
|
||||
if (mutex_lock_with_timeout(global_lock)) {
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
}
|
||||
|
||||
void stupid_timeout() {
|
||||
if (!mutex_lock_with_timeout(global_lock)) {
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
}
|
||||
|
||||
void reversal() {
|
||||
mutex_lock(global_lock);
|
||||
mutex_lock(global_lock2);
|
||||
|
||||
mutex_unlock(global_lock);
|
||||
mutex_unlock(global_lock2);
|
||||
}
|
||||
|
||||
|
||||
// Trying to repro the false positives unsuccessfully...
|
||||
extern bool decision();
|
||||
|
||||
inline void __attribute__((always_inline)) locker() {
|
||||
mutex_lock(global_lock);
|
||||
}
|
||||
|
||||
inline void __attribute__((always_inline)) unlocker() {
|
||||
mutex_unlock(global_lock);
|
||||
}
|
||||
|
||||
static inline void __attribute__((always_inline)) lock_wrap() {
|
||||
locker();
|
||||
if (decision()) {
|
||||
unlocker();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void __attribute__((always_inline)) unlock_wrap() {
|
||||
unlocker();
|
||||
}
|
||||
|
||||
void lockme() {
|
||||
lock_wrap();
|
||||
unlock_wrap();
|
||||
}
|
133
checkers/test-programs/syscall-test.c
Normal file
133
checkers/test-programs/syscall-test.c
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
extern bool syscall_internal_elevate_privilege();
|
||||
extern void syscall_assert_userspace_buffer(const void * check_buffer, int size);
|
||||
|
||||
extern void * app_malloc(unsigned size);
|
||||
|
||||
void do_stuff(void * buffer, int size) {
|
||||
strncpy(buffer, "Woooooooo", size);
|
||||
}
|
||||
|
||||
void good_syscall(void * buffer, int size) {
|
||||
syscall_internal_elevate_privilege();
|
||||
|
||||
syscall_assert_userspace_buffer(buffer, size);
|
||||
|
||||
do_stuff(buffer, size);
|
||||
}
|
||||
|
||||
void bad_syscall(void * buffer, int size) {
|
||||
syscall_internal_elevate_privilege();
|
||||
do_stuff(buffer, size);
|
||||
}
|
||||
|
||||
void stupid_syscall(void * buffer, int size) {
|
||||
void * stupid = (char *)buffer + 1;
|
||||
syscall_internal_elevate_privilege();
|
||||
do_stuff(stupid, size);
|
||||
}
|
||||
|
||||
void not_syscall(void * buffer, int size) {
|
||||
do_stuff(buffer, size);
|
||||
}
|
||||
|
||||
void nested_syscall(void * buffer, int size) {
|
||||
syscall_internal_elevate_privilege();
|
||||
syscall_assert_userspace_buffer(buffer, size);
|
||||
bad_syscall(buffer, size);
|
||||
good_syscall(buffer, size);
|
||||
}
|
||||
|
||||
void bad_nested_syscall(void * buffer, int size) {
|
||||
syscall_internal_elevate_privilege();
|
||||
bad_syscall(buffer, size);
|
||||
}
|
||||
|
||||
void hidden_bad_syscall(void * buffer, int size) {
|
||||
syscall_internal_elevate_privilege();
|
||||
do_stuff(buffer, size);
|
||||
}
|
||||
|
||||
void if_syscall(void * buffer, int size) {
|
||||
if (syscall_internal_elevate_privilege()) {
|
||||
syscall_assert_userspace_buffer(buffer, size);
|
||||
}
|
||||
do_stuff(buffer, size);
|
||||
}
|
||||
|
||||
void wrapper() {
|
||||
void * buffer = NULL;
|
||||
int size = 0;
|
||||
|
||||
good_syscall(buffer, size);
|
||||
// This tests to make sure analysis continues through good_syscall
|
||||
hidden_bad_syscall(buffer, size);
|
||||
}
|
||||
|
||||
bool cond(const char *font_key) {
|
||||
return &cond == font_key;
|
||||
}
|
||||
|
||||
void conditional_syscall(const char *font_key) {
|
||||
syscall_internal_elevate_privilege();
|
||||
|
||||
if (font_key) {
|
||||
if (!cond(font_key)) {
|
||||
do_stuff(font_key, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void store_syscall(char * buf, int size) {
|
||||
syscall_internal_elevate_privilege();
|
||||
|
||||
buf[0] = 'a';
|
||||
char * new = buf;
|
||||
|
||||
do_stuff(new, size);
|
||||
|
||||
}
|
||||
|
||||
void load_syscall(char * buf, int size) {
|
||||
syscall_internal_elevate_privilege();
|
||||
|
||||
char test = buf[0];
|
||||
|
||||
do_stuff(&test, size);
|
||||
}
|
||||
|
||||
void bind_syscall(char * buf, int size) {
|
||||
syscall_internal_elevate_privilege();
|
||||
|
||||
char * new = buf;
|
||||
|
||||
do_stuff(new, size);
|
||||
}
|
||||
|
||||
void malloc_syscall() {
|
||||
syscall_internal_elevate_privilege();
|
||||
void *buf = app_malloc(5);
|
||||
|
||||
syscall_assert_userspace_buffer(buf, 5);
|
||||
|
||||
do_stuff(buf, 5);
|
||||
}
|
70
checkers/wscript
Normal file
70
checkers/wscript
Normal file
|
@ -0,0 +1,70 @@
|
|||
from os import path
|
||||
|
||||
def options(opt):
|
||||
opt.load('compiler_cxx')
|
||||
opt.add_option('--checker', action='store', default='all')
|
||||
|
||||
def configure(conf):
|
||||
conf.env.CXX = 'clang++'
|
||||
conf.load('compiler_cxx')
|
||||
|
||||
conf.env.append_value('DEFINES', ['__STDC_CONSTANT_MACROS',
|
||||
'__STDC_LIMIT_MACROS'])
|
||||
|
||||
conf.check_cfg(msg='Checking for llvm config',
|
||||
path='llvm-config',
|
||||
package='',
|
||||
args='--cxxflags --ldflags --libs --system-libs',
|
||||
uselib_store='LLVM')
|
||||
|
||||
clang_libs = ['clang',
|
||||
'clangARCMigrate',
|
||||
'clangAST',
|
||||
'clangASTMatchers',
|
||||
'clangAnalysis',
|
||||
'clangApplyReplacements',
|
||||
'clangBasic',
|
||||
'clangCodeGen',
|
||||
'clangDriver',
|
||||
'clangDynamicASTMatchers',
|
||||
'clangEdit',
|
||||
'clangFormat',
|
||||
'clangFrontend',
|
||||
'clangFrontendTool',
|
||||
'clangIndex',
|
||||
'clangLex',
|
||||
'clangParse',
|
||||
'clangQuery',
|
||||
'clangRename',
|
||||
'clangRewrite',
|
||||
'clangRewriteFrontend',
|
||||
'clangSema',
|
||||
'clangSerialization',
|
||||
'clangStaticAnalyzerCheckers',
|
||||
'clangStaticAnalyzerCore',
|
||||
'clangStaticAnalyzerFrontend',
|
||||
'clangTooling',
|
||||
'clangToolingCore']
|
||||
|
||||
conf.check_cxx(msg='Checking for clang++',
|
||||
uselib_store='CLANG',
|
||||
use=['LLVM'],
|
||||
lib=clang_libs)
|
||||
|
||||
def build(bld):
|
||||
checkers = []
|
||||
|
||||
if bld.options.checker == 'all':
|
||||
checkers = bld.path.ant_glob('*.cpp')
|
||||
else:
|
||||
checkers = [ bld.path.make_node(bld.options.checker) ]
|
||||
|
||||
for checker in checkers:
|
||||
source = [ checker ]
|
||||
target = checker.change_ext('.dylib')
|
||||
bld.shlib(source=source,
|
||||
target=target,
|
||||
use=['CLANG', 'LLVM'],
|
||||
cppflags=['-fno-rtti', '-std=c++11', '-fPIC'])
|
||||
|
||||
# vim:filetype=python
|
Loading…
Add table
Add a link
Reference in a new issue