Skip to content

Commit 3646d59

Browse files
committed
checkpoint
1 parent fde0df0 commit 3646d59

File tree

7 files changed

+257
-77
lines changed

7 files changed

+257
-77
lines changed

cpp/cert/src/rules/CON50-CPP/DoNotAllowAMutexToGoOutOfScopeWhileLocked.ql

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,28 @@ import semmle.code.cpp.dataflow.TaintTracking
2020

2121
/*
2222
* This query finds potential misuse of mutexes passed to threads by considering
23-
* cases where the underlying mutex is a stack variable; such a variable would
23+
* cases where the underlying mutex is a local variable; such a variable would
2424
* go out of scope at the end of the calling function and thus would potentially
25-
* create issues for the thread depending on the mutex.
25+
* create issues for the thread depending on the mutex. This query is primarily
26+
* targeted at C usages since in the case of CPP, many more cases can be covered
27+
* via tracking of destructors. The main difference is that this query doesn't
28+
* expect an explicitly deleted call to be made.
2629
*
27-
* It is of course possible to safely use such a pattern. A safe usage of this
28-
* pattern one would perform some sort of waiting or looping or control after
29-
* passing such a mutex in. For this reason we perform a broad analysis that
30-
* looks for waiting-like behavior following the call to `std::thread`. Since
31-
* there are a wide variety of correct behaviors that may be called we take the
32-
* very broad view that calling the `join` method subsequent to creating the
33-
* thread is enough to classify as "correct behavior."
30+
* In order to safely destroy a dependent mutex, it is necessary both to not delete
31+
* it, but also if deletes do happen, one must wait for a thread to exit prior to
32+
* deleting it. We broadly model this by using standard language support for thread
33+
* synchronization.
3434
*/
35+
from ThreadDependentMutex dm, LocalVariable lv
36+
where
37+
not isExcluded(dm.asExpr(), ConcurrencyPackage::doNotDestroyAMutexWhileItIsLockedQuery()) and
38+
not isExcluded(lv, ConcurrencyPackage::doNotDestroyAMutexWhileItIsLockedQuery()) and
39+
not lv.isStatic() and
40+
TaintTracking::localTaint(dm.getAUsage(), DataFlow::exprNode(lv.getAnAssignedValue()))
41+
// ensure that each dependent thread is followed by some sort of joining
42+
// behavior.
43+
and exists(DataFlow::Node n | n = dm.getADependentThreadCreationExpr() | forall(ThreadWait tw |
44+
not (tw = n.asExpr().getASuccessor*())
45+
))
3546

36-
from MutexDependentThreadConstructor cc, StackVariable sv
37-
where
38-
not isExcluded(cc, ConcurrencyPackage::doNotAllowAMutexToGoOutOfScopeWhileLockedQuery()) and
39-
not isExcluded(sv, ConcurrencyPackage::doNotAllowAMutexToGoOutOfScopeWhileLockedQuery()) and
40-
// find cases where stack local variable has flowed into a thread mutex argument
41-
DataFlow::localFlow(DataFlow::exprNode(sv.getAnAccess()), DataFlow::exprNode(cc.dependentMutex())) and
42-
// exempt cases where some "waiting like" behavior is detected
43-
not exists(ThreadWait tw |
44-
TaintTracking::localTaint(DataFlow::exprNode(cc), DataFlow::exprNode(tw.getQualifier()))
45-
)
46-
select cc, "Mutex used by thread potentially destroyed while in use."
47+
select dm, "Mutex used by thread potentially destroyed while in use."

cpp/cert/src/rules/CON50-CPP/DoNotDestroyAMutexWhileItIsLocked.ql

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,35 @@ import semmle.code.cpp.dataflow.TaintTracking
1919

2020
/*
2121
* This query finds potential misuse of mutexes passed to threads by considering
22-
* cases where the underlying mutex may be deleted explicitly. The scope of this
23-
* query is that it considers this behavior locally within the procedure. We do
24-
* this by looking for cases where the mutex is the target of a delete within
25-
* the same procedure following the creation of the thread.
22+
* cases where the underlying mutex may be destroyed. The scope of this query is
23+
* that it performs this analysis both locally within the function but can also
24+
* look through to the called thread to identify mutexes it may not own.
25+
* query is that it considers this behavior locally within the procedure.
2626
*
27-
* It is of course possible to safely use such a pattern. A safe usage of this
28-
* pattern one would perform some sort of waiting or looping or control after
29-
* passing such a mutex in. For this reason we perform a broad analysis that
30-
* looks for waiting-like behavior following the call to `std::thread`. Since
31-
* there are a wide variety of correct behaviors that may be called we take the
32-
* very broad view that calling the `join` method subsequent to creating the
33-
* thread is enough to classify as "correct behavior."
27+
* In order to safely destroy a dependent mutex, it is necessary both to not delete
28+
* it, but also if deletes do happen, one must wait for a thread to exit prior to
29+
* deleting it. We broadly model this by using standard language support for thread
30+
* synchronization.
3431
*/
35-
36-
from MutexDependentThreadConstructor cc, DeleteExpr deleteExpr, VariableAccess va
37-
where
38-
not isExcluded(cc, ConcurrencyPackage::doNotDestroyAMutexWhileItIsLockedQuery()) and
39-
deleteExpr = cc.getASuccessor*() and
40-
DataFlow::localFlow(DataFlow::exprNode(va), DataFlow::exprNode(cc.dependentMutex())) and
41-
deleteExpr.getExpr() = va.getTarget().getAnAccess() and
42-
// exempt cases where some "waiting like" behavior is detected
43-
not exists(ThreadWait tw |
44-
TaintTracking::localTaint(DataFlow::exprNode(cc), DataFlow::exprNode(tw.getQualifier()))
32+
from ThreadDependentMutex dm, MutexDestroyer md
33+
where
34+
not isExcluded(dm.asExpr(), ConcurrencyPackage::doNotDestroyAMutexWhileItIsLockedQuery()) and
35+
not isExcluded(md, ConcurrencyPackage::doNotDestroyAMutexWhileItIsLockedQuery()) and
36+
// find all instances where a usage of a dependent mutex flows into a
37+
// expression that will destroy it.
38+
TaintTracking::localTaint(dm.getAUsage(), DataFlow::exprNode(md.getMutexExpr()))
39+
and
40+
(
41+
// firstly, we assume it is never safe to destroy a global mutex, but it is
42+
// difficult to make assumptions about the intended control flow.
43+
not exists(dm.asExpr().getEnclosingFunction()) or
44+
// secondly, we assume it is never safe to destroy a mutex created by
45+
// another function scope -- which includes trying to destroy a mutex that
46+
// was passed into a function.
47+
not md.getMutexExpr().getEnclosingFunction() = dm.asExpr().getEnclosingFunction() or
48+
// this leaves only destructions of mutexes locally near the thread that may
49+
// consume them. We allow this only if there has been some effort to
50+
// synchronize the threads prior to destroying the mutex.
51+
not exists(ThreadWait tw | tw = md.getAPredecessor*())
4552
)
46-
select cc, "Mutex used by thread potentially deleted while in use."
53+
select dm, "Mutex used by thread potentially $@ while in use.", md, "deleted"

cpp/cert/src/rules/CON50-CPP/MutexDependentThreadConstructor.qll

Lines changed: 0 additions & 16 deletions
This file was deleted.

cpp/cert/src/rules/CON50-CPP/ThreadWait.qll

Lines changed: 0 additions & 13 deletions
This file was deleted.

cpp/cert/test/rules/CON50-CPP/test.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <stdatomic.h>
2+
#include <stddef.h>
3+
#include <threads.h>
4+
5+
mtx_t mx;
6+
7+
int t1(void *dummy) {
8+
mtx_lock(&mx);
9+
mtx_unlock(&mx);
10+
return 0;
11+
}
12+
13+
int f1() {
14+
thrd_t threads[5];
15+
16+
mtx_init(&mx, mtx_plain);
17+
18+
for (size_t i = 0; i < 5; i++) {
19+
thrd_create(&threads[i], t1, NULL);
20+
21+
}
22+
for (size_t i = 0; i < 5; i++) {
23+
thrd_join(threads[i], 0);
24+
}
25+
26+
mtx_destroy(&mx);
27+
return 0;
28+
}
29+
30+
int f2() {
31+
thrd_t threads[5];
32+
33+
mtx_init(&mx, mtx_plain);
34+
35+
for (size_t i = 0; i < 5; i++) {
36+
thrd_create(&threads[i], t1, NULL);
37+
}
38+
39+
mtx_destroy(&mx);
40+
return 0;
41+
}

cpp/cert/test/rules/CON50-CPP/test.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
void t1(int i, std::mutex *pm) {}
55
void t2(int i, std::mutex **pm) {}
6+
void t3(int i, std::mutex *pm) { delete pm; }
7+
68
void f1() {
79
std::thread threads[5];
810
std::mutex m1;
@@ -44,7 +46,13 @@ void f4() {
4446
std::thread threads[5];
4547

4648
for (int i = 0; i < 5; ++i) {
47-
threads[i] = std::thread(t1, i, m3); // NON_COMPLIANT
49+
threads[i] = std::thread(t1, i, m3); // COMPLIANT
50+
}
51+
52+
// since we wait here, and the local function created the
53+
// mutex, the following delete is allowed.
54+
for (int i = 0; i < 5; ++i) {
55+
threads[i].join();
4856
}
4957

5058
delete m3;
@@ -74,4 +82,30 @@ void f6() {
7482
}
7583

7684
delete m3;
77-
}
85+
}
86+
87+
void f7() {
88+
m3 = new std::mutex();
89+
90+
std::thread threads[5];
91+
92+
for (int i = 0; i < 5; ++i) {
93+
threads[i] = std::thread(
94+
t3, i, m3); // NON_COMPLIANT - t3 will attempt to delete the mutex.
95+
}
96+
97+
for (int i = 0; i < 5; ++i) {
98+
threads[i].join();
99+
}
100+
}
101+
102+
void f8() {
103+
std::mutex *m = new std::mutex();
104+
delete m; // COMPLIANT
105+
}
106+
107+
void f9() {
108+
std::mutex m; // COMPLIANT
109+
}
110+
111+
std::mutex *m4 = new std::mutex();

cpp/common/src/codingstandards/cpp/Concurrency.qll

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class ThreadConstructorCall extends ConstructorCall, ThreadCreationFunction {
4848
}
4949

5050
/**
51-
* Models a call to a thread constructor via `std::thread`.
51+
* Models a call to a thread constructor via `thrd_create`.
5252
*/
5353
class C11ThreadCreateCall extends ThreadCreationFunction {
5454
Function f;
@@ -458,13 +458,139 @@ class MutexDependentThreadConstructor extends ThreadConstructorCall {
458458
}
459459

460460
/**
461-
* Models a call to a a `std::thread` join.
461+
* Models thread waiting functions.
462462
*/
463-
class ThreadWait extends FunctionCall {
463+
abstract class ThreadWait extends FunctionCall { }
464+
465+
/**
466+
* Models a call to a `std::thread` join.
467+
*/
468+
class CPPThreadWait extends ThreadWait {
464469
VariableAccess var;
465470

466-
ThreadWait() {
471+
CPPThreadWait() {
467472
getTarget().(MemberFunction).getDeclaringType().hasQualifiedName("std", "thread") and
468473
getTarget().getName() = "join"
469474
}
470475
}
476+
477+
/**
478+
* Models a call to `thread_join` in C11.
479+
*/
480+
class C11ThreadWait extends FunctionCall {
481+
VariableAccess var;
482+
483+
C11ThreadWait() { getTarget().getName() = "thread_join" }
484+
}
485+
486+
abstract class MutexSource extends FunctionCall { }
487+
488+
/**
489+
* Models a C++ style mutex.
490+
*/
491+
class CPPMutexSource extends MutexSource, ConstructorCall {
492+
CPPMutexSource() { getTarget().getDeclaringType().hasQualifiedName("std", "mutex") }
493+
}
494+
495+
/**
496+
* Models a C11 style mutex.
497+
*/
498+
class C11MutexSource extends MutexSource, FunctionCall {
499+
C11MutexSource() { getTarget().hasName("mtx_init") }
500+
}
501+
502+
/**
503+
* Models a thread dependent mutex. A thread dependent mutex is a mutex
504+
* that is used by a thread.
505+
*/
506+
class ThreadDependentMutex extends DataFlow::Node {
507+
DataFlow::Node sink;
508+
509+
ThreadDependentMutex() {
510+
exists(ThreadDependentMutexTaintTrackingConfiguration config | config.hasFlow(this, sink))
511+
}
512+
513+
DataFlow::Node getASource() {
514+
// the source is either the thing that declared
515+
// the mutex
516+
result = this
517+
or
518+
// or the thread we are using it in
519+
result = getAThreadSource()
520+
}
521+
522+
/**
523+
* Gets the dataflow nodes corresponding to thread local usages of the
524+
* dependent mutex.
525+
*/
526+
DataFlow::Node getAThreadSource() {
527+
exists(FunctionCall fc, Function f, int n |
528+
fc.getArgument(n) = sink.asExpr() and
529+
f = fc.getArgument(0).(FunctionAccess).getTarget() and
530+
result = DataFlow::exprNode(f.getParameter(n - 1).getAnAccess())
531+
)
532+
}
533+
534+
/**
535+
* Produces the set of dataflow nodes to thread creation for threads
536+
* that are dependent on this mutex.
537+
*/
538+
DataFlow::Node getADependentThreadCreationExpr() {
539+
exists(FunctionCall fc |
540+
fc.getAnArgument() = sink.asExpr() and
541+
result = DataFlow::exprNode(fc)
542+
)
543+
}
544+
545+
/**
546+
* Gets a set of usages of this mutex in both the local and thread scope.
547+
*/
548+
DataFlow::Node getAUsage() { TaintTracking::localTaint(getASource(), result) }
549+
}
550+
551+
class ThreadDependentMutexTaintTrackingConfiguration extends TaintTracking::Configuration {
552+
ThreadDependentMutexTaintTrackingConfiguration() {
553+
this = "ThreadDependentMutexTaintTrackingConfiguration"
554+
}
555+
556+
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof MutexSource }
557+
558+
override predicate isSink(DataFlow::Node node) {
559+
exists(ThreadCreationFunction f | f.getAnArgument() = node.asExpr())
560+
}
561+
}
562+
563+
/**
564+
* Models expressions that destroy mutexes.
565+
*/
566+
abstract class MutexDestroyer extends Expr {
567+
/**
568+
* Gets the expression that references the mutex being destroyed.
569+
*/
570+
abstract Expr getMutexExpr();
571+
}
572+
573+
/**
574+
* Models C style mutex destruction via `mtx_destroy`.
575+
*/
576+
class C11MutexDestroyer extends MutexDestroyer, FunctionCall {
577+
C11MutexDestroyer() { getTarget().getName() = "mtx_destroy" }
578+
579+
/**
580+
* Returns the `Expr` being destroyed.
581+
*/
582+
override Expr getMutexExpr() { result = getArgument(0) }
583+
}
584+
585+
/**
586+
* Models implicit or explicit calls to the destructor of a mutex, either via
587+
* a `delete` statement or a variable going out of scope.
588+
*/
589+
class DestructorMutexDestroyer extends MutexDestroyer, DestructorCall {
590+
DestructorMutexDestroyer() { getTarget().getDeclaringType().hasQualifiedName("std", "mutex") }
591+
592+
/**
593+
* Returns the `Expr` being deleted.
594+
*/
595+
override Expr getMutexExpr() { this.getQualifier() = result }
596+
}

0 commit comments

Comments
 (0)